Skip to main content

seekstorm/
index.rs

1use add_result::decode_positions_commit;
2use ahash::{AHashMap, AHashSet};
3use futures::future;
4use indexmap::IndexMap;
5use itertools::Itertools;
6use memmap2::{Mmap, MmapMut, MmapOptions};
7use model2vec_rs::model::StaticModel;
8use num::FromPrimitive;
9use num_derive::FromPrimitive;
10
11use search::{QueryType, Search};
12use serde::{Deserialize, Serialize};
13use serde_json::Value;
14use smallvec::SmallVec;
15use snowball_stemmers_rs::{Algorithm, Stemmer};
16use std::{
17    cmp,
18    collections::HashMap,
19    fmt::{self},
20    fs::{self, File},
21    io::{BufRead, BufReader, Read, Seek, Write},
22    path::Path,
23    sync::{Arc, LazyLock},
24    thread::available_parallelism,
25    time::Instant,
26};
27use symspell_complete_rs::{PruningRadixTrie, SymSpell};
28use tokio::sync::{RwLock, Semaphore};
29use utils::{read_u32, write_u16};
30use utoipa::ToSchema;
31
32#[cfg(feature = "zh")]
33use crate::word_segmentation::WordSegmentationTM;
34use crate::{
35    INDEX_RUNTIME,
36    add_result::{self, B, K, SIGMA},
37    clustering::{ClusterHeader, ParentMedoid},
38    commit::Commit,
39    geo_search::encode_morton_2_d,
40    highlighter::Highlight,
41    search::{
42        self, FacetFilter, Point, QueryFacet, QueryRewriting, Ranges, ResultObject, ResultSort,
43        ResultType, SearchLexicalShard, SearchMode,
44    },
45    tokenizer::tokenizer,
46    utils::{
47        self, read_u8_ref, read_u16, read_u16_ref, read_u32_ref, read_u64, read_u64_ref, write_f32,
48        write_f64, write_i8, write_i16, write_i32, write_i64, write_u32, write_u64,
49    },
50    vector::{Inference, Model, Precision, Quantization, VectorHeader, read_min_max},
51    vector_similarity::{TurboQuant, VectorSimilarity},
52};
53
54#[cfg(any(
55    all(
56        feature = "gxhash",
57        target_arch = "x86_64",
58        target_feature = "aes",
59        target_feature = "sse2"
60    ),
61    all(
62        feature = "gxhash",
63        target_arch = "aarch64",
64        target_feature = "aes",
65        target_feature = "neon"
66    )
67))]
68use gxhash::{gxhash32, gxhash64};
69
70#[cfg(not(any(
71    all(
72        feature = "gxhash",
73        target_arch = "x86_64",
74        target_feature = "aes",
75        target_feature = "sse2"
76    ),
77    all(
78        feature = "gxhash",
79        target_arch = "aarch64",
80        target_feature = "aes",
81        target_feature = "neon"
82    )
83)))]
84use ahash::RandomState;
85
86pub(crate) const FILE_PATH: &str = "files";
87pub(crate) const INDEX_FILENAME: &str = "index.bin";
88pub(crate) const DOCSTORE_FILENAME: &str = "docstore.bin";
89pub(crate) const DELETE_FILENAME: &str = "delete.bin";
90pub(crate) const SCHEMA_FILENAME: &str = "schema.json";
91pub(crate) const SYNONYMS_FILENAME: &str = "synonyms.json";
92pub(crate) const META_FILENAME: &str = "index.json";
93pub(crate) const FACET_FILENAME: &str = "facet.bin";
94pub(crate) const FACET_VALUES_FILENAME: &str = "facet.json";
95
96pub(crate) const DICTIONARY_FILENAME: &str = "dictionary.csv";
97pub(crate) const COMPLETIONS_FILENAME: &str = "completions.csv";
98
99pub(crate) const VERSION: &str = env!("CARGO_PKG_VERSION");
100
101pub(crate) const VECTOR_FILENAME: &str = "vector.bin";
102
103const INDEX_HEADER_SIZE: u64 = 4;
104/// Incompatible index  format change: new library can't open old format, and old library can't open new format
105pub const INDEX_FORMAT_VERSION_MAJOR: u16 = 6;
106/// Backward compatible format change: new library can open old format, but old library can't open new format
107pub const INDEX_FORMAT_VERSION_MINOR: u16 = 1;
108
109/// Maximum processed positions per term per document: default=65_536. E.g. 65,536 * 'the' per document, exceeding positions are ignored for search.
110pub const MAX_POSITIONS_PER_TERM: usize = 65_536;
111pub(crate) const STOP_BIT: u8 = 0b10000000;
112pub(crate) const FIELD_STOP_BIT_1: u8 = 0b0010_0000;
113pub(crate) const FIELD_STOP_BIT_2: u8 = 0b0100_0000;
114/// maximum number of documents per block
115pub const ROARING_BLOCK_SIZE: usize = 65_536;
116
117pub(crate) const SPEEDUP_FLAG: bool = true;
118pub(crate) const SORT_FLAG: bool = true;
119
120pub(crate) const POSTING_BUFFER_SIZE: usize = 400_000_000;
121pub(crate) const MAX_QUERY_TERM_NUMBER: usize = 100;
122pub(crate) const SEGMENT_KEY_CAPACITY: usize = 1000;
123
124use tabled::Tabled;
125
126/// Information about the index, such as number of documents, number of terms, index size, etc. Displayed in the console.
127#[derive(Tabled, Clone)]
128pub struct Info {
129    /// Label
130    pub entry: &'static str,
131    /// Value
132    pub value: String,
133}
134
135/// Search request object
136#[derive(Deserialize, Serialize, Clone, ToSchema, Debug)]
137pub struct SearchRequestObject {
138    /// Query string, search operators + - "" are recognized.
139    #[serde(rename = "query")]
140    pub query_string: String,
141    /// Optional query vector: If None, then the query vector is derived from the query string using the specified model. If Some, then the query vector is used for semantic search and the query string is only used for lexical search and highlighting.
142    #[serde(default)]
143    pub query_vector: Option<Value>,
144    #[serde(default)]
145    #[schema(required = false, default = false, example = false)]
146    /// Enable empty query: if true, an empty query string iterates through all indexed documents, supporting the query parameters: offset, length, query_facets, facet_filter, result_sort,
147    /// otherwise an empty query string returns no results.
148    /// Typical use cases include index browsing, index export, conversion, analytics, audits, and inspection.
149    pub enable_empty_query: bool,
150    #[serde(default)]
151    #[schema(required = false, minimum = 0, default = 0, example = 0)]
152    /// Offset of search results to return.
153    pub offset: usize,
154    /// Number of search results to return.
155    #[serde(default = "length_api")]
156    #[schema(required = false, minimum = 1, default = 10, example = 10)]
157    pub length: usize,
158    #[serde(default)]
159    /// Specify the type of search result.
160    pub result_type: ResultType,
161    /// True realtime search: include indexed, but uncommitted documents into search results.
162    #[serde(default)]
163    pub realtime: bool,
164    /// Specify field names where to create keyword-in-context fragments and highlight query terms.
165    #[serde(default)]
166    pub highlights: Vec<Highlight>,
167    /// Specify field names where to search at querytime, whereas SchemaField.indexed is set at indextime. If empty then all indexed fields are searched.
168    #[schema(required = false, example = json!(["title"]))]
169    #[serde(default)]
170    pub field_filter: Vec<String>,
171    /// Specify names of fields to return in the search results, where SchemaField.store is set at indextime. If empty then all stored fields are returned.
172    #[serde(default)]
173    pub fields: Vec<String>,
174    /// Specify distance fields to derive at query time and return in the search results.
175    #[serde(default)]
176    pub distance_fields: Vec<DistanceField>,
177    /// Facets to return with search results: if empty then no facets are returned. Facets are only enabled on facet fields that are defined in schema at create_index!
178    #[serde(default)]
179    pub query_facets: Vec<QueryFacet>,
180    /// Facet filters to filter search results by facet values: if empty then no facet filters are applied. Facet filters are only enabled on facet fields that are defined in schema at create_index!
181    #[serde(default)]
182    pub facet_filter: Vec<FacetFilter>,
183    /// Sort field and order:
184    /// Search results are sorted by the specified facet field, either in ascending or descending order.
185    /// If no sort field is specified, then the search results are sorted by rank in descending order per default.
186    /// Multiple sort fields are combined by a "sort by, then sort by"-method ("tie-breaking"-algorithm).
187    /// The results are sorted by the first field, and only for those results where the first field value is identical (tie) the results are sub-sorted by the second field,
188    /// until the n-th field value is either not equal or the last field is reached.
189    /// A special _score field (BM25x), reflecting how relevant the result is for a given search query (phrase match, match in title etc.) can be combined with any of the other sort fields as primary, secondary or n-th search criterium.
190    /// Sort is only enabled on facet fields that are defined in schema at create_index!
191    /// Examples:
192    /// - result_sort = vec![ResultSort {field: "price".into(), order: SortOrder::Descending, base: FacetValue::None},ResultSort {field: "language".into(), order: SortOrder::Ascending, base: FacetValue::None}];
193    /// - result_sort = vec![ResultSort {field: "location".into(),order: SortOrder::Ascending, base: FacetValue::Point(vec![38.8951, -77.0364])}];
194    #[schema(required = false, example = json!([{"field": "date", "order": "Ascending", "base": "None" }]))]
195    #[serde(default)]
196    pub result_sort: Vec<ResultSort>,
197    /// Specify default query type: (default=Intersection). This can be overwritten by search operator within the query string (+-"").
198    #[schema(required = false, example = QueryType::Intersection)]
199    #[serde(default = "query_type_api")]
200    pub query_type_default: QueryType,
201    /// Specify query rewriting method for search query correction and completion: (default=SearchOnly).
202    #[schema(required = false, example = QueryRewriting::SearchOnly)]
203    #[serde(default = "query_rewriting_api")]
204    pub query_rewriting: QueryRewriting,
205    /// Specify search mode: (default=Lexical).
206    #[schema(required = false, example = SearchMode::Lexical)]
207    #[serde(default = "search_mode_api")]
208    pub search_mode: SearchMode,
209}
210
211fn search_mode_api() -> SearchMode {
212    SearchMode::Lexical
213}
214
215fn query_type_api() -> QueryType {
216    QueryType::Intersection
217}
218
219fn query_rewriting_api() -> QueryRewriting {
220    QueryRewriting::SearchOnly
221}
222
223fn length_api() -> usize {
224    10
225}
226
227#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
228/// Search result object
229pub struct SearchResultObject {
230    /// Time taken to execute the search query in nanoseconds
231    pub time: u128,
232    /// Search query string
233    pub original_query: String,
234    /// Search query string after any automatic query correction or completion
235    pub query: String,
236    /// Offset of the returned search results
237    pub offset: usize,
238    /// Number of requested search results
239    pub length: usize,
240    /// Number of returned search results matching the query
241    pub count: usize,
242    /// Total number of search results matching the query
243    pub count_total: usize,
244    /// Vector of search query terms. Can be used e.g. for custom highlighting.
245    pub query_terms: Vec<String>,
246    #[schema(value_type=Vec<HashMap<String, serde_json::Value>>)]
247    /// Vector of search result documents
248    pub results: Vec<Document>,
249    #[schema(value_type=HashMap<String, Vec<(String, usize)>>)]
250    /// Facets with their values and corresponding document counts
251    pub facets: AHashMap<String, Facet>,
252    /// Suggestions for query correction or completion
253    pub suggestions: Vec<String>,
254}
255
256/// Quota per API key
257#[derive(Default, Debug, Clone, Deserialize, Serialize, ToSchema)]
258pub struct ApikeyQuotaObject {
259    /// number of indices per API key
260    pub indices_max: usize,
261    /// combined index size per API key in MB
262    pub indices_size_max: usize,
263    /// combined number of documents in all indices per API key
264    pub documents_max: usize,
265    /// operations per month per API key: index/update/delete/query doc
266    pub operations_max: usize,
267    /// queries per sec per API key
268    pub rate_limit: Option<usize>,
269    /// for rate limit: time of first access within current window
270    #[serde(skip)]
271    #[schema(ignore)]
272    pub timestamp_nanos: usize,
273    #[serde(skip)]
274    #[schema(ignore)]
275    /// for rate limit: number of violations within current window
276    pub violation_count: usize,
277    /// create fixed demo api key instead of random api key for demo purposes (default=false)
278    #[serde(default)]
279    pub demo: bool,
280}
281
282#[derive(Deserialize, Serialize)]
283/// API key object
284pub struct ApikeyObject {
285    /// API key id: self maintained, also used for index directory path
286    pub id: u64,
287    /// self maintained, pure informational
288    pub apikey_hash: u128,
289    /// Quota per API key
290    pub quota: ApikeyQuotaObject,
291
292    /// list of index_id below this apikey
293    #[serde(skip)]
294    pub index_list: HashMap<u64, IndexArc>,
295}
296
297/// Create index request object
298#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
299pub struct CreateIndexRequest {
300    /// Index name, used for informational purposes only.
301    #[schema(example = "demo_index")]
302    pub index_name: String,
303    #[schema(required = true, example = json!([
304    {"field":"title","field_type":"Text","store":true,"index_lexical":true,"boost":10.0},
305    {"field":"body","field_type":"Text","store":true,"index_lexical":true,"longest":true},
306    {"field":"url","field_type":"Text","store":true,"index_lexical":false},
307    {"field":"date","field_type":"Timestamp","store":true,"index_lexical":false,"facet":true}]))]
308    /// Schema definition for the index: field name, field type, and indexing options. The schema defines how documents are indexed and searched. It specifies the fields that are indexed, stored, and used for faceting, as well as the field types and their properties. It also defines whether lexical, hybrid, or vector search is enabled for each field.
309    #[serde(default)]
310    pub schema: Vec<SchemaField>,
311    /// Specify similarity measure for the index: (default=Bm25fProximity). The similarity function is used to calculate the relevance score of search results for a given search query. The choice of similarity function can affect search performance and relevance, depending on the characteristics of the text being indexed and the search queries being executed.
312    #[serde(default = "similarity_type_api")]
313    pub similarity: LexicalSimilarity,
314    /// Specify tokenizer type for the index: (default=UnicodeAlphanumeric). The tokenizer is used to split text into tokens for indexing and searching. The choice of tokenizer can affect search performance and relevance, depending on the language and characteristics of the text being indexed.
315    #[serde(default = "tokenizer_type_api")]
316    pub tokenizer: TokenizerType,
317    #[serde(default)]
318    /// Specify stemmer
319    pub stemmer: StemmerType,
320    /// Specify stop words for the index. Stop words are not indexed and not searched for. This can be used to reduce index size and improve search performance by excluding high-frequency, low-information terms from the index.
321    #[serde(default)]
322    pub stop_words: StopwordType,
323    /// Specify frequent words for the index. Frequent words are used to optimize search performance for high-frequency terms.
324    #[serde(default)]
325    pub frequent_words: FrequentwordType,
326    /// Specify n-gram indexing for the index. N-gram indexing can improve search performance for certain types of queries.
327    /// The n-gram set is defined as a bitwise combination of the following values:
328    /// - NgramSet::SingleTerm = 0b00000000,,
329    /// - NgramSet::NgramFF = 0b00000001, (Ngram frequent frequent)
330    /// - NgramSet::NgramFR = 0b00000010, (Ngram frequent rare)
331    /// - NgramSet::NgramRF = 0b00000011, (Ngram rare frequent)
332    /// - NgramSet::NgramFFF = 0b00000100, (Ngram frequent frequent frequent)
333    /// - NgramSet::NgramRFF = 0b00000101, (Ngram rare frequent frequent)
334    /// - NgramSet::NgramFFR = 0b00000110, (Ngram frequent frequent rare)
335    /// - NgramSet::NgramFRF = 0b00000111, (Ngram frequent rare frequent)
336    ///
337    /// For example, to enable both NgramFF and NgramFFF, set ngram_indexing to 5 (1 | 4).
338    /// Note: enabling n-gram indexing (ngram_indexing>0) will increase index size and indexing time, but improves search performance of phrase queries with frequent terms.
339    #[serde(default = "ngram_indexing_api")]
340    pub ngram_indexing: u8,
341    /// Enable document compression for the index. This can reduce the index size on disk and in memory, but may increase indexing and search latency. Default: Snappy compression.
342    #[serde(default = "document_compression_api")]
343    pub document_compression: DocumentCompression,
344    /// Specify synonyms for the index. Synonyms are used to expand search queries with additional terms that have the same or similar meaning, improving recall and search relevance. The multiway option specifies whether the synonym relationship is multiway (if true, all terms in the synonym set are considered synonyms of each other) or one-way (if false, only the first term in the synonym set is considered the main term, and the other terms are considered synonyms of the main term).
345    #[schema(required = false, example = json!([{"terms":["berry","lingonberry","blueberry","gooseberry"],"multiway":false}]))]
346    #[serde(default)]
347    pub synonyms: Vec<Synonym>,
348    /// Enable spelling correction for search queries using the SymSpell algorithm.
349    /// When enabled, a SymSpell dictionary is incrementally created during indexing of documents and stored in the index.
350    /// In addition you need to set the parameter `query_rewriting` in the search method to enable it per query.
351    /// The creation of an individual dictionary derived from the indexed documents improves the correction quality compared to a generic dictionary.
352    /// An dictionary per index improves the privacy compared to a global dictionary derived from all indices.
353    /// The dictionary is deleted when delete_index or clear_index is called.
354    /// Note: enabling spelling correction increases the index size, indexing time and query latency.
355    /// Default: None. Enable by setting a value for max_dictionary_edit_distance (1..2 recommended).
356    /// The higher the value, the higher the number of errors that can be corrected - but also the memory consumption, lookup latency, and the number of false positives.
357    #[serde(default)]
358    pub spelling_correction: Option<SpellingCorrection>,
359    /// Enable query completion for search queries using a prefix dictionary. When enabled, a prefix dictionary is incrementally created during indexing of documents and stored in the index. The prefix dictionary is used to generate suggestions for query completion based on the indexed documents. In addition you need to set the parameter `query_rewriting` in the search method to enable it per query. Note: enabling query completion increases the index size, indexing time and query latency.
360    #[serde(default)]
361    pub query_completion: Option<QueryCompletion>,
362    #[serde(default)]
363    /// Enable clustering for vector search.
364    pub clustering: Clustering,
365    /// Enable inference for search and indexing. This can be used to create vector representations of documents and queries for semantic search, e.g. by using a model like PotionBase2M.
366    #[serde(default)]
367    pub inference: Inference,
368}
369
370fn similarity_type_api() -> LexicalSimilarity {
371    LexicalSimilarity::Bm25fProximity
372}
373
374fn tokenizer_type_api() -> TokenizerType {
375    TokenizerType::UnicodeAlphanumeric
376}
377
378fn ngram_indexing_api() -> u8 {
379    NgramSet::NgramFF as u8 | NgramSet::NgramFFF as u8
380}
381
382fn document_compression_api() -> DocumentCompression {
383    DocumentCompression::Snappy
384}
385
386#[derive(Debug, Clone, Deserialize, Serialize)]
387/// Delete API key request object
388pub struct DeleteApikeyRequest {
389    /// Base64 encoded API key to delete
390    pub apikey_base64: String,
391}
392
393/// Specifies which document ID to return
394#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
395pub struct GetIteratorRequest {
396    /// base document ID to start the iteration from
397    /// Use None to start from the beginning (take>0) or the end (take<0) of the index
398    /// In JSON use null for None
399    #[serde(default)]
400    pub document_id: Option<u64>,
401    /// the number of document IDs to skip
402    #[serde(default)]
403    pub skip: usize,
404    /// the number of document IDs to return
405    /// take>0: take next t document IDs, take<0: take previous t document IDs
406    #[serde(default = "default_1usize")]
407    pub take: isize,
408    /// if true, also deleted document IDs are included in the result
409    #[serde(default)]
410    pub include_deleted: bool,
411    /// if true, the documents are also retrieved along with their document IDs
412    #[serde(default)]
413    pub include_document: bool,
414    /// which fields to return (if include_document is true, if empty then return all stored fields)
415    #[serde(default)]
416    pub fields: Vec<String>,
417}
418
419fn default_1usize() -> isize {
420    1
421}
422
423/// Specifies which document and which field to return
424#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
425pub struct GetDocumentRequest {
426    /// query terms for highlighting
427    #[serde(default)]
428    pub query_terms: Vec<String>,
429    /// which fields to highlight: create keyword-in-context fragments and highlight terms
430    #[serde(default)]
431    pub highlights: Vec<Highlight>,
432    /// which fields to return
433    #[serde(default)]
434    pub fields: Vec<String>,
435    /// which distance fields to derive and return
436    #[serde(default)]
437    pub distance_fields: Vec<DistanceField>,
438}
439
440#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
441/// Index response object
442pub struct IndexResponseObject {
443    /// Index ID
444    pub id: u64,
445    /// Index name
446    #[schema(example = "demo_index")]
447    pub name: String,
448    #[schema(example = json!({
449        "title":{
450            "field":"title",
451            "store":true,
452            "index_lexical":true,
453            "field_type":"Text",
454            "boost":10.0,
455            "field_id":0
456        },
457        "body":{
458            "field":"body",
459            "store":true,
460            "index_lexical":true,
461            "field_type":"Text",
462            "field_id":1
463        },
464        "url":{
465           "field":"url",
466           "store":true,
467           "index_lexical":false,
468           "field_type":"Text",
469           "field_id":2
470        },
471        "date":{
472           "field":"date",
473           "store":true,
474           "index_lexical":false,
475           "field_type":"Timestamp",
476           "facet":true,
477           "field_id":3
478        }
479     }))]
480    /// Schema definition for the index
481    pub schema: HashMap<String, SchemaField>,
482    /// Number of indexed documents
483    pub indexed_doc_count: usize,
484    /// Number of committed documents
485    pub committed_doc_count: usize,
486    /// Number of operations: index, update, delete, queries
487    pub operations_count: u64,
488    /// Number of queries, for quotas and billing
489    pub query_count: u64,
490    /// SeekStorm version the index was created with
491    #[schema(example = "0.11.1")]
492    pub version: String,
493    /// Minimum and maximum values of numeric facet fields
494    #[schema(example = json!({"date":{"min":831306011,"max":1730901447}}))]
495    pub facets_minmax: HashMap<String, MinMaxFieldJson>,
496}
497
498/// A document is a flattened, single level of key-value pairs, where key is an arbitrary string, and value represents any valid JSON value.
499pub type Document = IndexMap<String, Value>;
500
501/// File type for storing documents: Path, Bytes, None.
502#[derive(Clone, PartialEq)]
503pub enum FileType {
504    /// File path
505    Path(Box<Path>),
506    /// File bytes
507    Bytes(Box<Path>, Box<[u8]>),
508    /// No file
509    None,
510}
511
512/// Compression type for document store
513#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, ToSchema)]
514pub enum DocumentCompression {
515    /// No compression (fastest, largest size)
516    None,
517    /// lz4 compression (fast compression/decompression (faster than snappy), medium size (larger than snappy))
518    Lz4,
519    /// snappy compression (fast compression/decompression (slower than lz4), medium size (smaller than lz4))
520    Snappy,
521    /// zstd compression level 1 (slowest compression/decompression, smallest size)
522    Zstd,
523}
524
525impl fmt::Display for DocumentCompression {
526    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
527        match self {
528            DocumentCompression::None => write!(f, "None"),
529            DocumentCompression::Lz4 => write!(f, "Lz4"),
530            DocumentCompression::Snappy => write!(f, "Snappy"),
531            DocumentCompression::Zstd => write!(f, "Zstd"),
532        }
533    }
534}
535
536/// Defines where the index resides during search:
537/// - Ram (the complete index is preloaded to Ram when opening the index)
538/// - Mmap (the index is accessed via memory-mapped files). See architecture.md for details.
539/// - At commit the data is serialized to disk for persistence both in Ram and Mmap mode.
540/// - The serialization format is identical for Ram and Mmap mode, allowing to change it retrospectively.
541#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
542pub enum AccessType {
543    /// Ram (the complete index is preloaded to Ram when opening the index).
544    /// - Index size is limited by available RAM size.
545    /// - Slightly fastesr search speed.
546    /// - Higher index loading time.
547    /// - Higher RAM usage.
548    Ram = 0,
549    /// Mmap (the index is accessed via memory-mapped files). See architecture.md for details.
550    /// - Enables index size scaling beyond RAM size.
551    /// - Slightly slower search speed compared to Ram.
552    /// - Faster index loading time compared to Ram.
553    /// - Lower RAM usage.
554    Mmap = 1,
555}
556
557/// Similarity type defines the scoring and ranking of the search results:
558/// - Bm25f: considers documents composed from several fields, with different field lengths and importance
559/// - Bm25fProximity: considers term proximity, e.g. for implicit phrase search with improved relevancy
560#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, Default, ToSchema)]
561pub enum LexicalSimilarity {
562    /// Bm25f considers documents composed from several fields, with different field lengths and importance
563    Bm25f = 0,
564    /// Bm25fProximity considers term proximity, e.g. for implicit phrase search with improved relevancy
565    #[default]
566    Bm25fProximity = 1,
567}
568
569impl fmt::Display for LexicalSimilarity {
570    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
571        match self {
572            LexicalSimilarity::Bm25f => write!(f, "Bm25f"),
573            LexicalSimilarity::Bm25fProximity => write!(f, "Bm25fProximity"),
574        }
575    }
576}
577
578/// Defines tokenizer behavior:
579/// AsciiAlphabetic
580/// - Mainly for for benchmark compatibility
581/// - Only ASCII alphabetic chars are recognized as token.
582///
583/// UnicodeAlphanumeric
584/// - All Unicode alphanumeric chars are recognized as token.
585/// - Allows '+' '-' '#' in middle or end of a token: c++, c#, block-max.
586///
587/// UnicodeAlphanumericFolded
588/// - All Unicode alphanumeric chars are recognized as token.
589/// - Allows '+' '-' '#' in middle or end of a token: c++, c#, block-max.
590/// - Diacritics, accents, zalgo text, umlaut, bold, italic, full-width UTF-8 characters are converted into its basic representation.
591/// - Apostroph handling prevents that short term parts preceding or following the apostroph get indexed (e.g. "s" in "someone's").
592/// - Tokenizing might be slower due to folding and apostroph processing.
593///
594/// UnicodeAlphanumericZH
595/// - Implements Chinese word segmentation to segment continuous Chinese text into tokens for indexing and search.
596/// - Supports mixed Latin and Chinese texts
597/// - Supports Chinese sentence boundary chars for KWIC snippets ahd highlighting.
598/// - Requires feature #[cfg(feature = "zh")]
599#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, Copy, Default, ToSchema)]
600pub enum TokenizerType {
601    /// Only ASCII alphabetic chars are recognized as token. Mainly for benchmark compatibility.
602    #[default]
603    AsciiAlphabetic = 0,
604    /// All Unicode alphanumeric chars are recognized as token.
605    /// Allow '+' '-' '#' in middle or end of a token: c++, c#, block-max.
606    UnicodeAlphanumeric = 1,
607    /// All Unicode alphanumeric chars are recognized as token.
608    /// Allows '+' '-' '#' in middle or end of a token: c++, c#, block-max.
609    /// Diacritics, accents, zalgo text, umlaut, bold, italic, full-width UTF-8 characters are converted into its basic representation.
610    /// Apostroph handling prevents that short term parts preceding or following the apostroph get indexed (e.g. "s" in "someone's").
611    /// Tokenizing might be slower due to folding and apostroph processing.
612    UnicodeAlphanumericFolded = 2,
613    /// Tokens are separated by whitespace. Mainly for benchmark compatibility.
614    Whitespace = 3,
615    /// Tokens are separated by whitespace. Token are converted to lowercase. Mainly for benchmark compatibility.
616    WhitespaceLowercase = 4,
617    /// Implements Chinese word segmentation to segment continuous Chinese text into tokens for indexing and search.
618    /// Supports mixed Latin and Chinese texts
619    /// Supports Chinese sentence boundary chars for KWIC snippets ahd highlighting.
620    /// Requires feature #[cfg(feature = "zh")]
621    #[cfg(feature = "zh")]
622    UnicodeAlphanumericZH = 5,
623}
624
625impl fmt::Display for TokenizerType {
626    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
627        match self {
628            TokenizerType::AsciiAlphabetic => write!(f, "AsciiAlphabetic"),
629            TokenizerType::UnicodeAlphanumeric => write!(f, "UnicodeAlphanumeric"),
630            TokenizerType::UnicodeAlphanumericFolded => write!(f, "UnicodeAlphanumericFolded"),
631            TokenizerType::Whitespace => write!(f, "Whitespace"),
632            TokenizerType::WhitespaceLowercase => write!(f, "WhitespaceLowercase"),
633            #[cfg(feature = "zh")]
634            TokenizerType::UnicodeAlphanumericZH => write!(f, "UnicodeAlphanumericZH"),
635        }
636    }
637}
638
639/// Defines stemming behavior, reducing inflected words to their word stem, base or root form.
640/// Stemming increases recall, but decreases precision. It can introduce false positive results.
641#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, Copy, Default, ToSchema)]
642pub enum StemmerType {
643    /// No stemming
644    #[default]
645    None = 0,
646    /// Arabic stemmer
647    Arabic = 1,
648    /// Armenian stemmer
649    Armenian = 2,
650    /// Basque stemmer
651    Basque = 3,
652    /// Catalan stemmer
653    Catalan = 4,
654    /// Czech stemmer
655    Czech = 5,
656    /// Danish stemmer
657    Danish = 6,
658    /// Dutch stemmer
659    Dutch = 7,
660    /// Dutch porter stemmer
661    DutchPorter = 8,
662    /// English stemmer
663    English = 9,
664    /// Esperanto stemmer
665    Esperanto = 10,
666    /// Estonian stemmer
667    Estonian = 11,
668    /// Finnish stemmer
669    Finnish = 12,
670    /// French stemmer
671    French = 13,
672    /// German stemmer
673    German = 14,
674    /// Greek stemmer
675    Greek = 15,
676    /// Hindi stemmer
677    Hindi = 16,
678    /// Hungarian stemmer
679    Hungarian = 17,
680    /// Indonesian stemmer
681    Indonesian = 18,
682    /// Irish stemmer
683    Irish = 19,
684    /// Italian stemmer
685    Italian = 20,
686    /// Lithuanian stemmer
687    Lithuanian = 21,
688    /// Lovins stemmer
689    Lovins = 22,
690    /// Nepali stemmer
691    Nepali = 23,
692    /// Norwegian stemmer
693    Norwegian = 24,
694    /// Persian stemmer
695    Persian = 25,
696    /// Polish stemmer
697    Polish = 26,
698    /// Porter stemmer
699    Porter = 27,
700    /// Portuguese stemmer
701    Portuguese = 28,
702    /// Romanian stemmer
703    Romanian = 29,
704    /// Russian stemmer
705    Russian = 30,
706    /// Serbian stemmer
707    Serbian = 31,
708    /// Sesotho stemmer
709    Sesotho = 32,
710    /// Spanish stemmer
711    Spanish = 33,
712    /// Swedish stemmer
713    Swedish = 34,
714    /// Tamil stemmer
715    Tamil = 35,
716    /// Turkish stemmer
717    Turkish = 36,
718    /// Ukrainian stemmer
719    Ukrainian = 37,
720    /// Yiddish stemmer
721    Yiddish = 38,
722}
723
724impl fmt::Display for StemmerType {
725    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
726        match self {
727            StemmerType::None => write!(f, "None"),
728            StemmerType::Arabic => write!(f, "Arabic"),
729            StemmerType::Armenian => write!(f, "Armenian"),
730            StemmerType::Basque => write!(f, "Basque"),
731            StemmerType::Catalan => write!(f, "Catalan"),
732            StemmerType::Czech => write!(f, "Czech"),
733            StemmerType::Danish => write!(f, "Danish"),
734            StemmerType::Dutch => write!(f, "Dutch"),
735            StemmerType::DutchPorter => write!(f, "DutchPorter"),
736            StemmerType::English => write!(f, "English"),
737            StemmerType::Esperanto => write!(f, "Esperanto"),
738            StemmerType::Estonian => write!(f, "Estonian"),
739            StemmerType::Finnish => write!(f, "Finnish"),
740            StemmerType::French => write!(f, "French"),
741            StemmerType::German => write!(f, "German"),
742            StemmerType::Greek => write!(f, "Greek"),
743            StemmerType::Hindi => write!(f, "Hindi"),
744            StemmerType::Hungarian => write!(f, "Hungarian"),
745            StemmerType::Indonesian => write!(f, "Indonesian"),
746            StemmerType::Irish => write!(f, "Irish"),
747            StemmerType::Italian => write!(f, "Italian"),
748            StemmerType::Lithuanian => write!(f, "Lithuanian"),
749            StemmerType::Lovins => write!(f, "Lovins"),
750            StemmerType::Nepali => write!(f, "Nepali"),
751            StemmerType::Norwegian => write!(f, "Norwegian"),
752            StemmerType::Persian => write!(f, "Persian"),
753            StemmerType::Polish => write!(f, "Polish"),
754            StemmerType::Porter => write!(f, "Porter"),
755            StemmerType::Portuguese => write!(f, "Portuguese"),
756            StemmerType::Romanian => write!(f, "Romanian"),
757            StemmerType::Russian => write!(f, "Russian"),
758            StemmerType::Serbian => write!(f, "Serbian"),
759            StemmerType::Sesotho => write!(f, "Sesotho"),
760            StemmerType::Spanish => write!(f, "Spanish"),
761            StemmerType::Swedish => write!(f, "Swedish"),
762            StemmerType::Tamil => write!(f, "Tamil"),
763            StemmerType::Turkish => write!(f, "Turkish"),
764            StemmerType::Ukrainian => write!(f, "Ukrainian"),
765            StemmerType::Yiddish => write!(f, "Yiddish"),
766        }
767    }
768}
769
770pub(crate) struct LevelIndex {
771    pub document_length_compressed_array: Vec<[u8; ROARING_BLOCK_SIZE]>,
772
773    pub docstore_pointer_docs: Vec<u8>,
774    pub docstore_pointer_docs_pointer: usize,
775    pub document_length_compressed_array_pointer: usize,
776}
777
778/// Posting lists are divided into blocks of a doc id range of 65.536 (16 bit).
779/// Each block can be compressed with a different method.
780#[derive(Default, Debug, Deserialize, Serialize, Clone)]
781pub(crate) struct BlockObjectIndex {
782    pub max_block_score: f32,
783    pub block_id: u32,
784    pub compression_type_pointer: u32,
785    pub posting_count: u16,
786    pub max_docid: u16,
787    pub max_p_docid: u16,
788    pub pointer_pivot_p_docid: u16,
789}
790
791/// PostingListObjectIndex owns all blocks of a postinglist of a term
792#[derive(Default)]
793pub(crate) struct PostingListObjectIndex {
794    pub posting_count: u32,
795    pub posting_count_ngram_1: u32,
796    pub posting_count_ngram_2: u32,
797    pub posting_count_ngram_3: u32,
798    pub posting_count_ngram_1_compressed: u8,
799    pub posting_count_ngram_2_compressed: u8,
800    pub posting_count_ngram_3_compressed: u8,
801    pub max_list_score: f32,
802    pub blocks: Vec<BlockObjectIndex>,
803
804    pub position_range_previous: u32,
805}
806
807#[derive(Default, Debug, Deserialize, Serialize, Clone)]
808pub(crate) struct PostingListObject0 {
809    pub pointer_first: usize,
810    pub pointer_last: usize,
811    pub posting_count: usize,
812
813    pub max_block_score: f32,
814    pub max_docid: u16,
815    pub max_p_docid: u16,
816
817    pub ngram_type: NgramType,
818    pub term_ngram1: String,
819    pub term_ngram2: String,
820    pub term_ngram3: String,
821    pub posting_count_ngram_1: f32,
822    pub posting_count_ngram_2: f32,
823    pub posting_count_ngram_3: f32,
824    pub posting_count_ngram_1_compressed: u8,
825    pub posting_count_ngram_2_compressed: u8,
826    pub posting_count_ngram_3_compressed: u8,
827
828    pub position_count: usize,
829    pub pointer_pivot_p_docid: u16,
830    pub size_compressed_positions_key: usize,
831    pub docid_delta_max: u16,
832    pub docid_old: u16,
833    pub compression_type_pointer: u32,
834}
835
836/// Type of posting list compression.
837#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, FromPrimitive)]
838pub(crate) enum CompressionType {
839    Delta = 0,
840    Array = 1,
841    Bitmap = 2,
842    Rle = 3,
843}
844
845pub(crate) struct QueueObject<'a> {
846    pub query_list: Vec<PostingListObjectQuery<'a>>,
847    pub query_index: usize,
848    pub max_score: f32,
849}
850
851/// PostingListObjectQuery manages thes posting list for each unique query term during intersection.
852#[derive(Clone)]
853pub(crate) struct PostingListObjectQuery<'a> {
854    pub posting_count: u32,
855    pub max_list_score: f32,
856    pub blocks: &'a Vec<BlockObjectIndex>,
857    pub blocks_index: usize,
858
859    pub term: String,
860    pub key0: u32,
861
862    pub compression_type: CompressionType,
863    pub rank_position_pointer_range: u32,
864    pub compressed_doc_id_range: usize,
865    pub pointer_pivot_p_docid: u16,
866
867    pub posting_pointer: usize,
868    pub posting_pointer_previous: usize,
869
870    pub byte_array: &'a [u8],
871
872    pub p_block: i32,
873    pub p_block_max: i32,
874    pub p_docid: usize,
875    pub p_docid_count: usize,
876
877    pub rangebits: i32,
878    pub docid: i32,
879    pub bitposition: u32,
880
881    pub intersect: u64,
882    pub ulong_pos: usize,
883
884    pub run_end: i32,
885    pub p_run: i32,
886    pub p_run_count: i32,
887    pub p_run_sum: i32,
888
889    pub term_index_unique: usize,
890    pub positions_count: u32,
891    pub positions_pointer: u32,
892
893    pub idf: f32,
894    pub idf_ngram1: f32,
895    pub idf_ngram2: f32,
896    pub idf_ngram3: f32,
897    pub tf_ngram1: u32,
898    pub tf_ngram2: u32,
899    pub tf_ngram3: u32,
900    pub ngram_type: NgramType,
901
902    pub end_flag: bool,
903    pub end_flag_block: bool,
904    pub is_embedded: bool,
905    pub embedded_positions: [u32; 4],
906    pub field_vec: SmallVec<[(u16, usize); 2]>,
907    pub field_vec_ngram1: SmallVec<[(u16, usize); 2]>,
908    pub field_vec_ngram2: SmallVec<[(u16, usize); 2]>,
909    pub field_vec_ngram3: SmallVec<[(u16, usize); 2]>,
910    pub bm25_flag: bool,
911}
912
913pub(crate) static DUMMY_VEC: Vec<BlockObjectIndex> = Vec::new();
914pub(crate) static DUMMY_VEC_8: Vec<u8> = Vec::new();
915
916impl Default for PostingListObjectQuery<'_> {
917    fn default() -> Self {
918        Self {
919            posting_count: 0,
920            max_list_score: 0.0,
921            blocks: &DUMMY_VEC,
922            blocks_index: 0,
923            term: "".to_string(),
924            key0: 0,
925            compression_type: CompressionType::Delta,
926            rank_position_pointer_range: 0,
927            compressed_doc_id_range: 0,
928            pointer_pivot_p_docid: 0,
929            posting_pointer: 0,
930            posting_pointer_previous: 0,
931            byte_array: &DUMMY_VEC_8,
932            p_block: 0,
933            p_block_max: 0,
934            p_docid: 0,
935            p_docid_count: 0,
936            rangebits: 0,
937            docid: 0,
938            bitposition: 0,
939            run_end: 0,
940            p_run: 0,
941            p_run_count: 0,
942            p_run_sum: 0,
943            term_index_unique: 0,
944            positions_count: 0,
945            positions_pointer: 0,
946            idf: 0.0,
947            idf_ngram1: 0.0,
948            idf_ngram2: 0.0,
949            idf_ngram3: 0.0,
950            ngram_type: NgramType::SingleTerm,
951            is_embedded: false,
952            embedded_positions: [0; 4],
953            field_vec: SmallVec::new(),
954            tf_ngram1: 0,
955            tf_ngram2: 0,
956            tf_ngram3: 0,
957            field_vec_ngram1: SmallVec::new(),
958            field_vec_ngram2: SmallVec::new(),
959            field_vec_ngram3: SmallVec::new(),
960
961            end_flag: false,
962            end_flag_block: false,
963            bm25_flag: true,
964            intersect: 0,
965            ulong_pos: 0,
966        }
967    }
968}
969
970/// NonUniquePostingListObjectQuery manages these posting list for each non-unique query term during intersection.
971/// It references to the unique query terms.
972#[derive(Clone)]
973pub(crate) struct NonUniquePostingListObjectQuery<'a> {
974    pub term_index_unique: usize,
975    pub term_index_nonunique: usize,
976    pub pos: u32,
977    pub p_pos: i32,
978    pub positions_pointer: usize,
979    pub positions_count: u32,
980    pub byte_array: &'a [u8],
981    pub key0: u32,
982    pub is_embedded: bool,
983    pub embedded_positions: [u32; 4],
984    pub p_field: usize,
985    pub field_vec: SmallVec<[(u16, usize); 2]>,
986}
987
988/// Terms are converted to hashs. The index is divided into key hash range partitioned segments.
989/// for each strip (key hash range) a separate dictionary (key hash - posting list) is maintained.
990/// The index hash multiple segments, each segments contains multiple terms, each term has a postinglist, each postinglist has multiple blocks.
991pub(crate) struct SegmentIndex {
992    pub byte_array_blocks: Vec<Vec<u8>>,
993    pub byte_array_blocks_pointer: Vec<(usize, usize, u32)>,
994    pub segment: AHashMap<u64, PostingListObjectIndex>,
995}
996
997/// StripObject0 defines a strip (key hash range) within level 0. Level 0 is the mutable level where all writes are taking place.
998/// After each 65.536 docs the level 0 is flushed as an immutable block to the next level
999#[derive(Default, Debug, Clone)]
1000pub(crate) struct SegmentLevel0 {
1001    pub segment: AHashMap<u64, PostingListObject0>,
1002    pub positions_compressed: Vec<u8>,
1003}
1004
1005/// FieldType defines the type of a field in the document: u8, u16, u32, u64, i8, i16, i32, i64, f32, f64, point, string, stringset, text.
1006#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, Default, ToSchema)]
1007pub enum FieldType {
1008    /// Unsigned 8-bit integer
1009    U8,
1010    /// Unsigned 16-bit integer
1011    U16,
1012    /// Unsigned 32-bit integer
1013    U32,
1014    /// Unsigned 64-bit integer
1015    U64,
1016    /// Signed 8-bit integer
1017    I8,
1018    /// Signed 16-bit integer
1019    I16,
1020    /// Signed 32-bit integer
1021    I32,
1022    /// Signed 64-bit integer
1023    I64,
1024    /// Timestamp is identical to I64, but to be used for Unix timestamps <https://en.wikipedia.org/wiki/Unix_time>.
1025    /// The reason for a separate FieldType is to enable the UI to interpret I64 as timestamp without using the field name as indicator.
1026    /// For date facets and filtering.
1027    Timestamp,
1028    /// Floating point 32-bit
1029    F32,
1030    /// Floating point 64-bit
1031    F64,
1032    /// Boolean
1033    Bool,
1034    /// String16
1035    /// allows a maximum cardinality of 65_535 (16 bit) distinct values, is space-saving.
1036    /// For faceting and filtering with low cardinality fields, e.g. boolean fields, enum fields, country, language, status, category, tags, etc.
1037    String16,
1038    /// String32
1039    /// allows a maximum cardinality of 4_294_967_295 (32 bit) distinct values
1040    /// For faceting and filtering with low cardinality fields, e.g. boolean fields, enum fields, country, language, status, category, tags, etc.
1041    String32,
1042    /// StringSet16 is a set of strings, e.g. tags, categories, keywords, authors, genres, etc.
1043    /// allows a maximum cardinality of 65_535 (16 bit) distinct values, is space-saving.
1044    /// For faceting and filtering with low cardinality fields, e.g. boolean fields, enum fields, country, language, status, category, tags, etc.
1045    StringSet16,
1046    /// StringSet32 is a set of strings, e.g. tags, categories, keywords, authors, genres, etc.
1047    /// allows a maximum cardinality of 4_294_967_295 (32 bit) distinct values
1048    /// For faceting and filtering with low cardinality fields, e.g. boolean fields, enum fields, country, language, status, category, tags, etc.
1049    StringSet32,
1050    /// Point is a geographic field type: A `Vec<f64>` with two coordinate values (latitude and longitude) are internally encoded into a single u64 value (Morton code).
1051    /// Morton codes enable efficient range queries.
1052    /// Latitude and longitude are a pair of numbers (coordinates) used to describe a position on the plane of a geographic coordinate system.
1053    /// The numbers are in decimal degrees format and range from -90 to 90 for latitude and -180 to 180 for longitude.
1054    /// Coordinates are internally stored as u64 morton code: both f64 values are multiplied by 10_000_000, converted to i32 and bitwise interleaved into a single u64 morton code
1055    /// The conversion between longitude/latitude coordinates and Morton code is lossy due to rounding errors.
1056    Point,
1057    /// Text is a text field, that will be tokenized by the selected Tokenizer into string tokens.
1058    #[default]
1059    Text,
1060    /// Hierarchical JSON Object, that will be tokenized by the selected Tokenizer into string tokens.
1061    /// The text is extracted from all levels of the JSON object and combined into a single text string (values only, not keys).
1062    /// In that respect, it is similar to combined fields in other search engines.
1063    /// Despite being indexed as a single text string, the hierarchical structure of an JSON object is preserved in the stored document, so that it can be retrieved in the search results.
1064    /// When using search with highlighting, make sure to use a `name` different from `field`, otherwise the Json `field` in the results will be overwritten with the highlight (snippet) text.
1065    Json,
1066    /// Binary encodes and stores binary data in base64 format. This field type will not be tokenized and indexed.
1067    /// For embedding binary data, e.g. images, audio, video, pdf, … in JSON or CSV documents. A self-contained alternative to storing URLs to external resources.
1068    /// Using the [Data URI scheme](https://en.wikipedia.org/wiki/Data_URI_scheme) in JavaScript you can create an Image object and put the base64 as its src, including the data:image... part like this:
1069    ///```javascript
1070    /// var image = new Image();
1071    /// image.src = 'data:image/png;base64,iVBORw0K...';
1072    /// document.body.appendChild(image);
1073    ///```
1074    Binary,
1075}
1076
1077/// Defines synonyms for terms per index.
1078#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
1079pub struct Synonym {
1080    /// List of terms that are synonyms.
1081    pub terms: Vec<String>,
1082    /// Creates alternative versions of documents where in each copy a term is replaced with one of its synonyms.
1083    /// Doesn't impact the query latency, but does increase the index size.
1084    /// Multi-way synonyms (default): all terms are synonyms of each other.
1085    /// One-way synonyms: only the first term is a synonym of the following terms, but not vice versa.
1086    /// E.g. [street, avenue, road] will result in searches for street to return documents containing any of the terms street, avenue or road,
1087    /// but searches for avenue will only return documents containing avenue, but not documents containing street or road.
1088    /// Currently only single terms without spaces are supported.
1089    /// Synonyms are supported in result highlighting.
1090    /// The synonyms that were created with the synonyms parameter in create_index are stored in synonyms.json in the index directory contains  
1091    /// Can be manually modified, but becomes effective only after restart and only for newly indexed documents.
1092    #[serde(default = "default_as_true")]
1093    pub multiway: bool,
1094}
1095
1096fn default_as_true() -> bool {
1097    true
1098}
1099
1100/// Defines a field in index schema: field, stored, indexed , field_type, facet, boost.
1101#[derive(Debug, Clone, Deserialize, Serialize, ToSchema, Default)]
1102pub struct SchemaField {
1103    /// unique name of a field
1104    pub field: String,
1105    /// only stored fields are returned in the search results
1106    pub store: bool,
1107    /// only indexed fields can be searched
1108    /// Even if index_lexical is true in the schema, the field in the actual document is optional. You can index documents without that field, but you cannot search for that field in those documents.
1109    pub index_lexical: bool,
1110    /// only indexed fields can be searched
1111    /// Even if index_vector is true in the schema, the field in the actual document is optional. You can index documents without that field, but you cannot search for that field in those documents.
1112    #[serde(skip_serializing_if = "is_default_bool")]
1113    #[serde(default = "default_false")]
1114    pub index_vector: bool,
1115    /// type of a field
1116    pub field_type: FieldType,
1117    /// optional faceting for a field
1118    /// Faceting can be enabled both for string field type and numerical field types.
1119    /// both numerical and string fields can be indexed (indexed=true) and stored (stored=true) in the json document,
1120    /// but with field_facet=true they are additionally stored in a binary format, for fast faceting and sorting without docstore access (decompression, deserialization)
1121    #[serde(skip_serializing_if = "is_default_bool")]
1122    #[serde(default = "default_false")]
1123    pub facet: bool,
1124
1125    /// Indicate the longest field in schema.  
1126    /// Otherwise the longest field will be automatically detected in first index_document.
1127    /// Setting/detecting the longest field ensures efficient index encoding.
1128    #[serde(skip_serializing_if = "is_default_bool")]
1129    #[serde(default = "default_false")]
1130    pub longest: bool,
1131
1132    /// optional custom weight factor for Bm25 ranking
1133    #[serde(skip_serializing_if = "is_default_f32")]
1134    #[serde(default = "default_1")]
1135    pub boost: f32,
1136
1137    /// if both indexed=true and dictionary_source=true then the terms from this field are added to dictionary to the spelling correction dictionary.
1138    /// if disabled, then a manually generated dictionary can be used: {index_path}/dictionary.csv
1139    #[serde(skip_serializing_if = "is_default_bool")]
1140    #[serde(default = "default_false")]
1141    pub dictionary_source: bool,
1142
1143    /// if both indexed=true and completion_source=true then the n-grams (unigrams, bigrams, trigrams) from this field are added to the auto-completion list.
1144    /// if disabled, then a manually generated completion list can be used: {index_path}/completions.csv
1145    /// it is recommended to enable completion_source only for fields that contain short text with high-quality terms for auto-completion, e.g. title, author, category, product name, tags,
1146    /// in order to keep the extraction time and RAM requirement for completions low and the completions relevance high.
1147    #[serde(skip_serializing_if = "is_default_bool")]
1148    #[serde(default = "default_false")]
1149    pub completion_source: bool,
1150
1151    #[serde(skip)]
1152    pub(crate) indexed_field_id: usize,
1153    #[serde(skip_deserializing)]
1154    pub(crate) field_id: usize,
1155}
1156
1157/// Defines a field in index schema: field, stored, indexed , field_type, facet, boost.
1158/// # Parameters
1159/// - field: unique name of a field
1160/// - store: only stored fields are returned in the search results
1161/// - index_lexical: index field into lexical index, only indexed fields can be searched.
1162/// - index_vector: index field into vector index, only indexed fields can be searched.
1163/// - field_type: type of a field: u8, u16, u32, u64, i8, i16, i32, i64, f32, f64, point
1164/// - facet: enable faceting for a field: for sorting results by field values, for range filtering, for result count per field value or range
1165/// - `longest`: This allows to annotate (manually set) the longest field in schema.  
1166///   Otherwise the longest field will be automatically detected in first index_document.
1167///   Setting/detecting the longest field ensures efficient index encoding.
1168/// - boost: optional custom weight factor for Bm25 ranking
1169/// # Returns
1170/// - SchemaField
1171/// # Example
1172/// ```rust
1173/// use seekstorm::index::{SchemaField, FieldType};
1174/// let schema_field = SchemaField::new("title".to_string(), true, true, false, FieldType::String16, false, false, 1.0, false, false);
1175/// ```
1176impl SchemaField {
1177    /// Creates a new SchemaField.
1178    #[allow(clippy::too_many_arguments)]
1179    pub fn new(
1180        field: String,
1181        store: bool,
1182        index_lexical: bool,
1183        index_vector: bool,
1184        field_type: FieldType,
1185        facet: bool,
1186        longest: bool,
1187        boost: f32,
1188        dictionary_source: bool,
1189        completion_source: bool,
1190    ) -> Self {
1191        SchemaField {
1192            field,
1193            store,
1194            index_lexical,
1195            index_vector,
1196            field_type,
1197            facet,
1198            longest,
1199            boost,
1200            dictionary_source,
1201            completion_source,
1202
1203            indexed_field_id: 0,
1204            field_id: 0,
1205        }
1206    }
1207}
1208
1209fn default_false() -> bool {
1210    false
1211}
1212
1213fn is_default_bool(num: &bool) -> bool {
1214    !(*num)
1215}
1216
1217fn default_1() -> f32 {
1218    1.0
1219}
1220
1221fn is_default_f32(num: &f32) -> bool {
1222    *num == 1.0
1223}
1224
1225pub(crate) struct IndexedField {
1226    pub schema_field_name: String,
1227    pub field_length_sum: usize,
1228    pub indexed_field_id: usize,
1229
1230    pub is_longest_field: bool,
1231}
1232
1233/// StopwordType defines the stopword behavior: None, English, German, French, Spanish, Custom.
1234/// Stopwords are removed, both from index and query: for compact index size and faster queries.
1235/// Stopword removal has drawbacks: “The Who”, “Take That”, “Let it be”, “To be or not to be”, "The The", "End of days", "What might have been" are all valid queries for bands, songs, movies, literature,
1236/// but become impossible when stopwords are removed.
1237/// The lists of stop_words and frequent_words should not overlap.
1238#[derive(Debug, Clone, Deserialize, Serialize, Default, ToSchema)]
1239pub enum StopwordType {
1240    /// No stopwords
1241    #[default]
1242    None,
1243    /// English stopwords
1244    English,
1245    /// German stopwords
1246    German,
1247    /// French stopwords
1248    French,
1249    /// Spanish stopwords
1250    Spanish,
1251    /// Custom stopwords
1252    Custom {
1253        ///List of stopwords.
1254        terms: Vec<String>,
1255    },
1256}
1257
1258/// FrequentwordType defines the frequentword behavior: None, English, German, French, Spanish, Custom.
1259/// Adjacent frequent terms are combined to bi-grams, both in index and query: for shorter posting lists and faster phrase queries (only for bi-grams of frequent terms).
1260/// The lists of stop_words and frequent_words should not overlap.
1261#[derive(Debug, Clone, Deserialize, Serialize, Default, ToSchema)]
1262pub enum FrequentwordType {
1263    /// No frequent words
1264    None,
1265    /// English frequent words
1266    #[default]
1267    English,
1268    /// German frequent words
1269    German,
1270    /// French frequent words
1271    French,
1272    /// Spanish frequent words
1273    Spanish,
1274    /// Custom frequent words
1275    Custom {
1276        ///List of frequent terms, max. 256 terms.
1277        terms: Vec<String>,
1278    },
1279}
1280
1281/// Defines spelling correction (fuzzy search) and dictionary generation settings for an index.
1282#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
1283pub struct SpellingCorrection {
1284    /// The edit distance thresholds for suggestions: 1..2 recommended; higher values increase latency and memory consumption.
1285    pub max_dictionary_edit_distance: usize,
1286    /// Term length thresholds for each edit distance.
1287    ///   None:    max_dictionary_edit_distance for all terms lengths
1288    ///   Some(\[4\]):    max_dictionary_edit_distance for all terms lengths >= 4,
1289    ///   Some(\[2,8\]):    max_dictionary_edit_distance for all terms lengths >=2, max_dictionary_edit_distance +1 for all terms for lengths>=8
1290    pub term_length_threshold: Option<Vec<usize>>,
1291
1292    /// The minimum frequency count for dictionary words to be considered eligible for spelling correction.
1293    /// Depends on the corpus size, 1..20 recommended.
1294    /// If count_threshold is too high, some correct words might be missed from the dictionary and deemed misspelled,
1295    /// if count_threshold is too low, some misspelled words from the corpus might be considered correct and added to the dictionary.
1296    /// Dictionary terms eligible for spelling correction (frequency count >= count_threshold) consume much more RAM, than the candidates (frequency count < count_threshold),  
1297    /// but the terms below count_threshold will be included in dictionary.csv too.
1298    pub count_threshold: usize,
1299
1300    /// Limits the maximum number of dictionary entries (terms >= count_threshold) to generate during indexing, preventing excessive RAM consumption.
1301    /// The number of terms in dictionary.csv will be higher, because it contains also the terms < count_threshold, to become eligible in the future during incremental dictionary updates.
1302    /// Dictionary terms eligible for spelling correction (frequency count >= count_threshold) consume much more RAM, than the candidates (frequency count < count_threshold).
1303    /// ⚠️ Above this threshold no new terms are added to the dictionary, causing them to be deemed incorrect during spelling correction and possibly changed to similar terms that are in the dictionary.
1304    pub max_dictionary_entries: usize,
1305}
1306
1307/// Defines query completion generation for an index.
1308#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
1309pub struct QueryCompletion {
1310    /// Maximum number of completions to generate during indexing
1311    /// disabled if == 0
1312    pub max_completion_entries: usize,
1313}
1314
1315/// Clustering defines the clustering behavior for approximate nearest neighbor (ANN) search: None, Auto, Fixed(usize).
1316#[derive(Clone, Copy, Debug, Deserialize, Serialize, Default, ToSchema)]
1317pub enum Clustering {
1318    /// Exhaustive vector search, no clustering/ANN.
1319    None,
1320    /// The number of clusters is automatically determined depending on the number of vectors per level and shard.
1321    #[default]
1322    Auto,
1323    /// Set the number of clusters to a fixed value per level and shard.
1324    Fixed(usize),
1325}
1326
1327impl fmt::Display for Clustering {
1328    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1329        match self {
1330            Clustering::None => write!(f, "None"),
1331            Clustering::Auto => write!(f, "Auto"),
1332            Clustering::Fixed(value) => write!(f, "Fixed({})", value),
1333        }
1334    }
1335}
1336
1337/// Specifies LexicalSimilarity, TokenizerType and AccessType when creating an new index
1338#[derive(Debug, Clone, Deserialize, Serialize)]
1339pub struct IndexMetaObject {
1340    /// unique index ID
1341    /// Only used in SeekStorm server, not by the SeekStorm library itself.
1342    /// In the SeekStorm server with REST API, the index ID is used to specify the index (within the API key) where you want to index and search.
1343    pub id: u64,
1344    /// index name: used informational purposes
1345    pub name: String,
1346    /// LexicalSimilarity defines the scoring and ranking of the search results: Bm25f or Bm25fProximity
1347    pub lexical_similarity: LexicalSimilarity,
1348    /// TokenizerType defines the tokenizer behavior: AsciiAlphabetic, UnicodeAlphanumeric, UnicodeAlphanumericFolded, UnicodeAlphanumericZH
1349    pub tokenizer: TokenizerType,
1350    /// StemmerType defines the stemming behavior: None, Arabic, Armenian, Danish, Dutch, English, French, German, Greek, Hungarian, Italian, Norwegian, Portuguese, Romanian, Russian, Spanish, Swedish, Tamil, Turkish
1351    pub stemmer: StemmerType,
1352
1353    /// StopwordType defines the stopword behavior: None, English, German, French, Spanish, Custom.
1354    /// Stopwords are removed, both from index and query: for compact index size and faster queries.
1355    /// Stopword removal has drawbacks: “The Who”, “Take That”, “Let it be”, “To be or not to be”, "The The", "End of days", "What might have been" are all valid queries for bands, songs, movies, literature,
1356    /// but become impossible when stopwords are removed.
1357    /// The lists of stop_words and frequent_words should not overlap.
1358    #[serde(default)]
1359    pub stop_words: StopwordType,
1360    /// FrequentwordType defines the frequentword behavior: None, English, German, French, Spanish, Custom.
1361    /// Adjacent frequent terms are combined to bi-grams, both in index and query: for shorter posting lists and faster phrase queries (only for bi-grams of frequent terms).
1362    /// The lists of stop_words and frequent_words should not overlap.
1363    #[serde(default)]
1364    pub frequent_words: FrequentwordType,
1365    /// N-gram indexing: n-grams are indexed in addition to single terms, for fast phrase search, at the cost of higher index size
1366    /// Preference valid both for index time and query time. Any change requires reindexing.
1367    /// bitwise OR flags:
1368    /// SingleTerm = 0b00000000, always enabled in addition to the other optional NgramSet below
1369    /// NgramFF    = 0b00000001, frequent frequent
1370    /// NgramFR    = 0b00000010, frequent rare
1371    /// NgramRF    = 0b00000100, rare frequent
1372    /// NgramFFF   = 0b00001000, frequent frequent frequent
1373    /// NgramRFF   = 0b00010000, rare frequent frequent
1374    /// NgramFFR   = 0b00100000, frequent frequent rare
1375    /// NgramFRF   = 0b01000000, frequent rare frequent
1376    /// When **minimum index size** is more important than phrase query latency, we recommend **Single Terms**:  
1377    /// `NgramSet::SingleTerm as u8`
1378    /// For a **good balance of latency and index size** cost, we recommend **Single Terms + Frequent Bigrams + Frequent Trigrams** (default):  
1379    /// `NgramSet::SingleTerm as u8 | NgramSet::NgramFF as u8 | NgramSet::NgramFFF`
1380    /// When **minimal phrase query latency** is more important than low index size, we recommend **Single Terms + Mixed Bigrams + Frequent Trigrams**:
1381    /// `NgramSet::SingleTerm as u8 | NgramSet::NgramFF as u8 | NgramSet::NgramFR as u8 | NgramSet::NgramRF | NgramSet::NgramFFF`
1382    #[serde(default = "ngram_indexing_default")]
1383    pub ngram_indexing: u8,
1384
1385    /// Compression algorithm for document store: None, Snappy, Lz4,Zstd
1386    #[serde(default = "doc_store_compression_default")]
1387    pub document_compression: DocumentCompression,
1388
1389    /// AccessType defines where the index resides during search: Ram or Mmap
1390    pub access_type: AccessType,
1391    /// Enable spelling correction for search queries using the SymSpell algorithm.
1392    /// SymSpell enables finding those spelling suggestions in a dictionary very fast with minimum Damerau-Levenshtein edit distance and maximum word occurrence frequency.
1393    /// When enabled, a SymSpell dictionary is incrementally created during indexing of documents and stored in the index.
1394    /// The spelling correction is not based on a generic dictionary, but on a domain specific one derived from your indexed documents (only indexed fields).
1395    /// This makes it language independent and prevents any discrepancy between corrected word and indexed content.
1396    /// The creation of an individual dictionary derived from the indexed documents improves the correction quality compared to a generic dictionary.
1397    /// An dictionary per index improves the privacy compared to a global dictionary derived from all indices.
1398    /// The dictionary is deleted when delete_index or clear_index is called.
1399    /// Note: enabling spelling correction increases the index size, indexing time and query latency.
1400    /// Default: None. Enable by setting CreateDictionary with values for max_dictionary_edit_distance (1..2 recommended) and optionally a term length thresholds for each edit distance.
1401    /// The higher the value, the higher the number of errors taht can be corrected - but also the memory consumption, lookup latency, and the number of false positives.
1402    /// ⚠️ In addition to the create_index parameter `meta.spelling_correction` you also need to set the parameter `query_rewriting` in the search method to enable it per query.
1403    #[serde(default)]
1404    pub spelling_correction: Option<SpellingCorrection>,
1405
1406    /// Enable query completion for search queries
1407    /// When enabled, an auto-completion list is incrementally created during indexing of documents and stored in the index.
1408    /// Because the completions are not based on a generic dictionary, but on a domain specific one derived from your indexed documents (only from indexed fields with completion_source=true), this increases the relevance of completions.
1409    /// ⚠️ Deriving completions from indexed documents increases the indexing time and index size.
1410    #[serde(default)]
1411    pub query_completion: Option<QueryCompletion>,
1412
1413    /// clustering defines the clustering behavior for approximate nearest neighbor (ANN) search: None, Auto, Fixed(usize).
1414    #[serde(default)]
1415    pub clustering: Clustering,
1416
1417    /// vector source: External vectors field (Json array field or Base64 encoded text field) or internal vector generation from text field via model2vec
1418    #[serde(default)]
1419    pub inference: Inference,
1420}
1421
1422fn ngram_indexing_default() -> u8 {
1423    NgramSet::NgramFF as u8 | NgramSet::NgramFFF as u8
1424}
1425
1426fn doc_store_compression_default() -> DocumentCompression {
1427    DocumentCompression::Snappy
1428}
1429
1430#[derive(Debug, Clone, Default)]
1431pub(crate) struct ResultFacet {
1432    pub field: String,
1433    pub values: AHashMap<u32, usize>,
1434    pub prefix: String,
1435    pub length: u32,
1436    pub ranges: Ranges,
1437}
1438
1439/// DistanceUnit defines the unit for distance calculation: kilometers or miles.
1440#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ToSchema)]
1441pub enum DistanceUnit {
1442    /// Kilometers
1443    Kilometers,
1444    /// Miles
1445    Miles,
1446}
1447
1448/// DistanceField defines a field for proximity search.
1449#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
1450pub struct DistanceField {
1451    /// field name of a numeric facet field (currently onyl Point field type supported)
1452    pub field: String,
1453    /// field name of the distance field we are deriving from the numeric facet field (Point type) and the base (Point type)
1454    pub distance: String,
1455    /// base point (lat,lon) for distance calculation
1456    pub base: Point,
1457    /// distance unit for the distance field: kilometers or miles
1458    pub unit: DistanceUnit,
1459}
1460
1461impl Default for DistanceField {
1462    fn default() -> Self {
1463        DistanceField {
1464            field: String::new(),
1465            distance: String::new(),
1466            base: Vec::new(),
1467            unit: DistanceUnit::Kilometers,
1468        }
1469    }
1470}
1471
1472/// MinMaxField represents the minimum and maximum value of a field.
1473#[derive(Deserialize, Serialize, Debug, Clone, Default)]
1474pub struct MinMaxField {
1475    /// minimum value of the field
1476    pub min: ValueType,
1477    /// maximum value of the field
1478    pub max: ValueType,
1479}
1480
1481/// MinMaxFieldJson is a JSON representation of the minimum and maximum value of a field.
1482#[derive(Deserialize, Serialize, Debug, Clone, Default, ToSchema)]
1483pub struct MinMaxFieldJson {
1484    /// minimum value of the field
1485    pub min: Value,
1486    /// maximum value of the field
1487    pub max: Value,
1488}
1489
1490/// Value type for a field: u8, u16, u32, u64, i8, i16, i32, i64, f32, f64, point, none.
1491#[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq)]
1492pub enum ValueType {
1493    /// Unsigned 8-bit integer
1494    U8(u8),
1495    /// Unsigned 16-bit integer
1496    U16(u16),
1497    /// Unsigned 32-bit integer
1498    U32(u32),
1499    /// Unsigned 64-bit integer
1500    U64(u64),
1501    /// Signed 8-bit integer
1502    I8(i8),
1503    /// Signed 16-bit integer
1504    I16(i16),
1505    /// Signed 32-bit integer
1506    I32(i32),
1507    /// Signed 64-bit integer
1508    I64(i64),
1509    /// Unix timestamp: the number of seconds since 1 January 1970
1510    Timestamp(i64),
1511    /// Floating point 32-bit
1512    F32(f32),
1513    /// Floating point 64-bit
1514    F64(f64),
1515    /// Geographic Point: a pair of latitude and longitude coordinates and a distance unit (kilometers, miles)
1516    Point(Point, DistanceUnit),
1517    /// No value
1518    #[default]
1519    None,
1520}
1521
1522/// Facet field, with field name and a map of unique values and their count (number of times the specific value appears in the whole index).
1523#[derive(Deserialize, Serialize, Debug, Clone, Default)]
1524pub struct FacetField {
1525    /// Facet field name
1526    pub name: String,
1527    /// Vector of facet value names and their count
1528    /// The number of distinct string values and numerical ranges per facet field (cardinality) is limited to 65_536.
1529    /// Once that number is reached, the facet field is not updated anymore (no new values are added, no existing values are counted).
1530    pub values: IndexMap<String, (Vec<String>, usize)>,
1531
1532    ///Minimum value of the facet field
1533    pub min: ValueType,
1534    ///Maximum value of the facet field
1535    pub max: ValueType,
1536
1537    #[serde(skip)]
1538    pub(crate) offset: usize,
1539    #[serde(skip)]
1540    pub(crate) field_type: FieldType,
1541}
1542
1543/// Facet field, with field name and a vector of unique values and their count (number of times the specific value appears in the whole index).
1544/// Facet field: a vector of unique values and their count (number of times the specific value appears in the whole index).
1545pub type Facet = Vec<(String, usize)>;
1546
1547/// Shard wrapped in Arc and RwLock for concurrent read and write access.
1548pub type ShardArc = Arc<RwLock<Shard>>;
1549
1550/// Index wrapped in Arc and RwLock for concurrent read and write access.
1551pub type IndexArc = Arc<RwLock<Index>>;
1552
1553/// The shard object of the index. It contains all levels and all segments of the index.
1554/// It also contains all properties that control indexing and intersection.
1555pub struct Shard {
1556    /// Incompatible index  format change: new library can't open old format, and old library can't open new format
1557    pub index_format_version_major: u16,
1558    /// Backward compatible format change: new library can open old format, but old library can't open new format
1559    pub index_format_version_minor: u16,
1560
1561    /// Number of indexed documents
1562    pub indexed_doc_count: usize,
1563    /// Number of indexed vectors
1564    pub(crate) indexed_vector_count: usize,
1565    /// Number of indexed clusters
1566    pub(crate) indexed_cluster_count: usize,
1567
1568    /// Number of committed documents
1569    pub committed_doc_count: usize,
1570    /// The index countains indexed, but uncommitted documents. Documents will either committed automatically once the number exceeds 64K documents **per shard**, or once commit is invoked manually.
1571    pub(crate) uncommitted: bool,
1572
1573    /// Indicates whether the index has been modified since the start.
1574    pub(crate) modified: bool,
1575
1576    /// Defines a field in index schema: field, stored, indexed , field_type, facet, boost.
1577    pub schema_map: HashMap<String, SchemaField>,
1578    /// List of stored fields in the index: get_document and highlighter work only with stored fields
1579    pub stored_field_names: Vec<String>,
1580    /// Specifies LexicalSimilarity, TokenizerType and AccessType when creating an new index
1581    pub meta: IndexMetaObject,
1582
1583    pub(crate) is_last_level_incomplete: bool,
1584    pub(crate) last_level_index_file_start_pos: u64,
1585    pub(crate) last_level_docstore_file_start_pos: u64,
1586    pub(crate) last_level_vector_file_start_pos: u64,
1587
1588    /// Number of allowed parallel indexed documents (default=available_parallelism). Can be used to detect wehen all indexing processes are finished.
1589    pub(crate) semaphore: Arc<Semaphore>,
1590    pub(crate) docstore_file: File,
1591    pub(crate) docstore_file_mmap: Mmap,
1592
1593    pub(crate) delete_file: File,
1594    pub(crate) delete_hashset: AHashSet<usize>,
1595
1596    pub(crate) index_file: File,
1597    pub(crate) index_path_string: String,
1598    pub(crate) index_file_mmap: Mmap,
1599
1600    pub(crate) compressed_index_segment_block_buffer: Vec<u8>,
1601    pub(crate) compressed_docstore_segment_block_buffer: Vec<u8>,
1602
1603    pub(crate) segment_number1: usize,
1604    pub(crate) segment_number_bits1: usize,
1605
1606    pub(crate) document_length_normalized_average: f32,
1607    pub(crate) positions_sum_normalized: u64,
1608
1609    pub(crate) level_index: Vec<LevelIndex>,
1610    pub(crate) segments_index: Vec<SegmentIndex>,
1611    pub(crate) segments_level0: Vec<SegmentLevel0>,
1612
1613    pub(crate) enable_fallback: bool,
1614    pub(crate) enable_single_term_topk: bool,
1615    pub(crate) enable_search_quality_test: bool,
1616    pub(crate) enable_inter_query_threading: bool,
1617    pub(crate) enable_inter_query_threading_auto: bool,
1618
1619    pub(crate) segment_number_mask1: u32,
1620
1621    pub(crate) indexed_field_vec: Vec<IndexedField>,
1622    pub(crate) indexed_field_id_bits: usize,
1623    pub(crate) indexed_field_id_mask: usize,
1624    pub(crate) longest_field_id: usize,
1625    pub(crate) longest_field_auto: bool,
1626    pub(crate) indexed_schema_vec: Vec<SchemaField>,
1627
1628    pub(crate) document_length_compressed_array: Vec<[u8; ROARING_BLOCK_SIZE]>,
1629    pub(crate) key_count_sum: u64,
1630
1631    pub(crate) block_id: usize,
1632    pub(crate) strip_compressed_sum: u64,
1633    pub(crate) postings_buffer: Vec<u8>,
1634    pub(crate) postings_buffer_pointer: usize,
1635
1636    pub(crate) size_compressed_positions_index: u64,
1637    pub(crate) size_compressed_docid_index: u64,
1638
1639    pub(crate) postinglist_count: usize,
1640    pub(crate) docid_count: usize,
1641    pub(crate) position_count: usize,
1642
1643    pub(crate) mute: bool,
1644    pub(crate) frequentword_results: AHashMap<String, ResultObject>,
1645
1646    pub(crate) facets: Vec<FacetField>,
1647    pub(crate) facets_map: AHashMap<String, usize>,
1648    pub(crate) facets_size_sum: usize,
1649    pub(crate) facets_file: File,
1650    pub(crate) facets_file_mmap: MmapMut,
1651    pub(crate) bm25_component_cache: [f32; 256],
1652
1653    pub(crate) string_set_to_single_term_id_vec: Vec<AHashMap<String, AHashSet<u32>>>,
1654
1655    pub(crate) synonyms_map: AHashMap<u64, SynonymItem>,
1656
1657    #[cfg(feature = "zh")]
1658    pub(crate) word_segmentation_option: Option<WordSegmentationTM>,
1659
1660    pub(crate) shard_number: usize,
1661    pub(crate) index_option: Option<Arc<RwLock<Index>>>,
1662    pub(crate) stemmer: Option<Stemmer>,
1663
1664    pub(crate) stop_words: AHashSet<String>,
1665    pub(crate) frequent_words: Vec<String>,
1666    pub(crate) frequent_hashset: AHashSet<u64>,
1667    pub(crate) key_head_size: usize,
1668    pub(crate) level_terms: AHashMap<u32, String>,
1669    pub(crate) level_completions: Arc<RwLock<AHashMap<Vec<String>, usize>>>,
1670    /// AVX2 (x86_64) support enabled.
1671    pub(crate) is_avx2: bool,
1672    /// NEON (aarch64) support enabled.
1673    pub(crate) is_neon: bool,
1674    /// Any SIMD backend (`is_avx2 || is_neon`). Call sites dispatch on this
1675    /// to pick between the `*_simd` SIMD path and the scalar fallback.
1676    pub(crate) is_simd: bool,
1677    pub(crate) is_vector_indexing: bool,
1678    pub(crate) is_lexical_indexing: bool,
1679    pub(crate) chunks_meta: Vec<(u16, u32, u32)>,
1680    pub(crate) chunks_string: Vec<String>,
1681    pub(crate) vector_file: File,
1682    pub(crate) vector_file_mmap: Mmap,
1683    pub(crate) block_vector_buffer: Vec<ParentMedoid>,
1684    pub(crate) vector_dimensions: usize,
1685    pub(crate) vector_dimensions_original: usize,
1686    pub(crate) vector_precision: Precision,
1687    pub(crate) quantization: Quantization,
1688    pub(crate) vector_similarity: VectorSimilarity,
1689    pub(crate) chunk_size: usize,
1690
1691    pub(crate) min_vector_value: f32,
1692    pub(crate) max_vector_value: f32,
1693    pub(crate) turbo_quant: TurboQuant,
1694}
1695
1696/// The root object of the index. It contains all levels and all segments of the index.
1697/// It also contains all properties that control indexing and intersection.
1698pub struct Index {
1699    pub(crate) docid_global: Arc<RwLock<usize>>,
1700
1701    /// Incompatible index  format change: new library can't open old format, and old library can't open new format
1702    pub index_format_version_major: u16,
1703    /// Backward compatible format change: new library can open old format, but old library can't open new format
1704    pub index_format_version_minor: u16,
1705
1706    /// Number of indexed documents
1707    pub(crate) indexed_doc_count: usize,
1708    /// Number of indexed vectors
1709    pub indexed_vector_count: usize,
1710    /// Number of indexed clusters
1711    pub indexed_cluster_count: usize,
1712
1713    /// Number of deleted documents
1714    pub(crate) deleted_doc_count: usize,
1715    /// Defines a field in index schema: field, stored, indexed , field_type, facet, boost.
1716    pub schema_map: HashMap<String, SchemaField>,
1717    /// List of stored fields in the index: get_document and highlighter work only with stored fields
1718    pub stored_field_names: Vec<String>,
1719    /// Specifies LexicalSimilarity, TokenizerType and AccessType when creating an new index
1720    pub meta: IndexMetaObject,
1721
1722    pub(crate) index_file: File,
1723    pub(crate) index_path_string: String,
1724
1725    pub(crate) compressed_index_segment_block_buffer: Vec<u8>,
1726
1727    pub(crate) segment_number1: usize,
1728    pub(crate) segment_number_mask1: u32,
1729
1730    pub(crate) indexed_field_vec: Vec<IndexedField>,
1731
1732    pub(crate) mute: bool,
1733
1734    pub(crate) facets: Vec<FacetField>,
1735
1736    pub(crate) synonyms_map: AHashMap<u64, SynonymItem>,
1737
1738    pub(crate) shard_number: usize,
1739    pub(crate) shard_vec: Vec<Arc<RwLock<Shard>>>,
1740
1741    pub(crate) max_dictionary_entries: usize,
1742    pub(crate) symspell_option: Option<Arc<RwLock<SymSpell>>>,
1743
1744    pub(crate) max_completion_entries: usize,
1745    pub(crate) completion_option: Option<Arc<RwLock<PruningRadixTrie>>>,
1746
1747    pub(crate) frequent_hashset: AHashSet<String>,
1748
1749    pub(crate) embedding_model_option: Option<StaticModel>,
1750    /// The precision of the vectors: Float32 or Int8
1751    pub vector_precision: Precision,
1752    pub(crate) quantization: Quantization,
1753    /// The dimensions of the vectors: e.g. 64, 128, 256, 512, 1024, 768, 1536, 2048, 4096
1754    /// When using TurboQuant, padded to the next power of two for the Fast Walsh-Hadamard Transform.
1755    pub vector_dimensions: usize,
1756    /// The original dimensions of the vectors.
1757    pub vector_dimensions_original: usize,
1758    /// Vector similarity function for approximate nearest neighbor (ANN) search: Cosine, Euclidean, DotProduct.
1759    pub vector_similarity: VectorSimilarity,
1760    /// AVX2 support enabled
1761    pub(crate) is_avx2: bool,
1762    /// NEON support enabled (aarch64 only).
1763    pub(crate) is_neon: bool,
1764    /// Any SIMD backend enabled (`is_avx2 || is_neon`).
1765    pub(crate) is_simd: bool,
1766    /// Indicates whether the index contains vector embeddings for vector search.
1767    pub(crate) is_vector_indexing: bool,
1768    /// Indicates whether the index contains lexical term indexing for lexical search.
1769    pub(crate) is_lexical_indexing: bool,
1770    pub(crate) chunk_size: usize,
1771    pub(crate) turbo_quant: TurboQuant,
1772}
1773
1774///SynonymItem is a vector of tuples: (synonym term, (64-bit synonym term hash, 64-bit synonym term hash))
1775pub type SynonymItem = Vec<(String, (u64, u32))>;
1776
1777/// Get the version of the SeekStorm search library
1778pub fn version() -> &'static str {
1779    VERSION
1780}
1781
1782pub(crate) fn get_synonyms_map(
1783    synonyms: &[Synonym],
1784    segment_number_mask1: u32,
1785) -> AHashMap<u64, SynonymItem> {
1786    let mut synonyms_map: AHashMap<u64, SynonymItem> = AHashMap::new();
1787    for synonym in synonyms.iter() {
1788        if synonym.terms.len() > 1 {
1789            let mut hashes: Vec<(String, (u64, u32))> = Vec::new();
1790            for term in synonym.terms.iter() {
1791                let term_bytes = term.to_lowercase();
1792                hashes.push((
1793                    term.to_string(),
1794                    (
1795                        hash64(term_bytes.as_bytes()),
1796                        hash32(term_bytes.as_bytes()) & segment_number_mask1,
1797                    ),
1798                ));
1799            }
1800            if synonym.multiway {
1801                for (i, hash) in hashes.iter().enumerate() {
1802                    let new_synonyms = if i == 0 {
1803                        hashes[1..].to_vec()
1804                    } else if i == hashes.len() - 1 {
1805                        hashes[..hashes.len() - 1].to_vec()
1806                    } else {
1807                        [&hashes[..i], &hashes[(i + 1)..]].concat()
1808                    };
1809
1810                    if let Some(item) = synonyms_map.get_mut(&hash.1.0) {
1811                        *item = item
1812                            .clone()
1813                            .into_iter()
1814                            .chain(new_synonyms)
1815                            .collect::<HashMap<String, (u64, u32)>>()
1816                            .into_iter()
1817                            .collect();
1818                    } else {
1819                        synonyms_map.insert(hash.1.0, new_synonyms);
1820                    }
1821                }
1822            } else {
1823                synonyms_map.insert(hashes[0].1.0, hashes[1..].to_vec());
1824            }
1825        }
1826    }
1827    synonyms_map
1828}
1829
1830/// N-gram indexing: n-grams are indexed in addition to single terms, for faster phrase search, at the cost of higher index size
1831/// Setting valid both for index time and query time. Any change requires reindexing.
1832/// bitwise OR flags:
1833#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, FromPrimitive)]
1834pub enum NgramSet {
1835    /// no n-grams, only single terms are indexed
1836    SingleTerm = 0b00000000,
1837    /// Ngram frequent frequent
1838    NgramFF = 0b00000001,
1839    /// Ngram frequent rare
1840    NgramFR = 0b00000010,
1841    /// Ngram rare frequent
1842    NgramRF = 0b00000100,
1843    /// Ngram frequent frequent frequent
1844    NgramFFF = 0b00001000,
1845    /// Ngram rare frequent frequent
1846    NgramRFF = 0b00010000,
1847    /// Ngram frequent frequent rare
1848    NgramFFR = 0b00100000,
1849    /// Ngram frequent rare frequent
1850    NgramFRF = 0b01000000,
1851}
1852
1853#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, FromPrimitive, Default)]
1854pub(crate) enum NgramType {
1855    /// no n-grams, only single terms are indexed
1856    #[default]
1857    SingleTerm = 0,
1858    /// Ngram frequent frequent
1859    NgramFF = 1,
1860    /// Ngram frequent rare
1861    NgramFR = 2,
1862    /// Ngram rare frequent
1863    NgramRF = 3,
1864    /// Ngram frequent frequent frequent
1865    NgramFFF = 4,
1866    /// Ngram rare frequent frequent
1867    NgramRFF = 5,
1868    /// Ngram frequent frequent rare
1869    NgramFFR = 6,
1870    /// Ngram frequent rare frequent
1871    NgramFRF = 7,
1872}
1873
1874/// Create index in RAM.
1875/// Inner data structures for create index and open_index
1876/// * `index_path` - index path.  
1877/// * `meta` - index meta object.  
1878/// * `schema` - schema.  
1879/// * `synonyms` - vector of synonyms.
1880/// * `segment_number_bits1` - number of index segments: e.g. 11 bits for 2048 segments.  
1881/// * `mute` - prevent emitting status messages (e.g. when using pipes for data interprocess communication).  
1882/// * `force_shard_number` - set number of shards manually or automatically.
1883///   - none: number of shards is set automatically = number of physical processor cores (default)
1884///   - small: slower indexing, higher latency, slightly higher throughput, faster realtime search, lower RAM consumption
1885///   - large: faster indexing, lower latency, slightly lower throughput, slower realtime search, higher RAM consumption
1886pub async fn create_index(
1887    index_path: &Path,
1888    meta: IndexMetaObject,
1889    schema: &Vec<SchemaField>,
1890    synonyms: &Vec<Synonym>,
1891    segment_number_bits1: usize,
1892    mute: bool,
1893    force_shard_number: Option<usize>,
1894) -> Result<IndexArc, String> {
1895    create_index_root(
1896        index_path,
1897        meta,
1898        schema,
1899        true,
1900        synonyms,
1901        segment_number_bits1,
1902        mute,
1903        force_shard_number,
1904    )
1905    .await
1906}
1907
1908#[allow(clippy::too_many_arguments)]
1909pub(crate) async fn create_index_root(
1910    index_path: &Path,
1911    meta: IndexMetaObject,
1912    #[allow(clippy::ptr_arg)] schema: &Vec<SchemaField>,
1913    serialize_schema: bool,
1914    synonyms: &Vec<Synonym>,
1915    segment_number_bits1: usize,
1916    mute: bool,
1917    force_shard_number: Option<usize>,
1918) -> Result<IndexArc, String> {
1919    let frequent_hashset: AHashSet<String> = match &meta.frequent_words {
1920        FrequentwordType::None => AHashSet::new(),
1921        FrequentwordType::English => FREQUENT_EN.lines().map(|x| x.to_string()).collect(),
1922        FrequentwordType::German => FREQUENT_EN.lines().map(|x| x.to_string()).collect(),
1923        FrequentwordType::French => FREQUENT_FR.lines().map(|x| x.to_string()).collect(),
1924        FrequentwordType::Spanish => FREQUENT_ES.lines().map(|x| x.to_string()).collect(),
1925        FrequentwordType::Custom { terms } => terms.iter().map(|x| x.to_string()).collect(),
1926    };
1927
1928    let segment_number1 = 1usize << segment_number_bits1;
1929    let segment_number_mask1 = (1u32 << segment_number_bits1) - 1;
1930
1931    let index_path_buf = index_path.to_path_buf();
1932    let index_path_string = index_path_buf.to_str().unwrap();
1933
1934    if !index_path.exists() {
1935        if !mute {
1936            println!("index path created: {} ", index_path_string);
1937        }
1938        fs::create_dir_all(index_path).unwrap();
1939    }
1940
1941    let file_path = Path::new(index_path_string).join(FILE_PATH);
1942    if !file_path.exists() {
1943        fs::create_dir_all(file_path).unwrap();
1944    }
1945
1946    match File::options()
1947        .read(true)
1948        .write(true)
1949        .create(true)
1950        .truncate(false)
1951        .open(Path::new(index_path).join(INDEX_FILENAME))
1952    {
1953        Ok(index_file) => {
1954            let mut is_vector_indexing = false;
1955            let mut is_lexical_indexing = false;
1956            let mut schema = schema.clone();
1957            for schema_field in schema.iter_mut() {
1958                if schema_field.field_type == FieldType::Binary && schema_field.index_lexical {
1959                    schema_field.index_lexical = false;
1960                }
1961                if schema_field.index_vector {
1962                    is_vector_indexing = true;
1963                }
1964                if schema_field.index_lexical {
1965                    is_lexical_indexing = true;
1966                }
1967            }
1968
1969            let mut document_length_compressed_array: Vec<[u8; ROARING_BLOCK_SIZE]> = Vec::new();
1970            let mut indexed_field_vec: Vec<IndexedField> = Vec::new();
1971            let mut facets_vec: Vec<FacetField> = Vec::new();
1972            let mut facets_map: AHashMap<String, usize> = AHashMap::new();
1973
1974            let mut schema_map: HashMap<String, SchemaField> = HashMap::new();
1975            let mut indexed_schema_vec: Vec<SchemaField> = Vec::new();
1976            let mut stored_field_names = Vec::new();
1977            let mut facets_size_sum = 0;
1978            let mut longest_field_id_option: Option<usize> = None;
1979            for (i, schema_field) in schema.iter().enumerate() {
1980                let mut schema_field_clone = schema_field.clone();
1981
1982                schema_field_clone.indexed_field_id = indexed_field_vec.len();
1983                if schema_field.longest && schema_field.index_lexical {
1984                    longest_field_id_option = Some(schema_field_clone.indexed_field_id);
1985                }
1986
1987                schema_field_clone.field_id = i;
1988                schema_map.insert(schema_field.field.clone(), schema_field_clone.clone());
1989
1990                if schema_field.facet {
1991                    let facet_size = match schema_field.field_type {
1992                        FieldType::U8 => 1,
1993                        FieldType::U16 => 2,
1994                        FieldType::U32 => 4,
1995                        FieldType::U64 => 8,
1996                        FieldType::I8 => 1,
1997                        FieldType::I16 => 2,
1998                        FieldType::I32 => 4,
1999                        FieldType::I64 => 8,
2000                        FieldType::Timestamp => 8,
2001                        FieldType::F32 => 4,
2002                        FieldType::F64 => 8,
2003                        FieldType::String16 => 2,
2004                        FieldType::String32 => 4,
2005                        FieldType::StringSet16 => 2,
2006                        FieldType::StringSet32 => 4,
2007                        FieldType::Point => 8,
2008                        _ => 1,
2009                    };
2010
2011                    facets_map.insert(schema_field.field.clone(), facets_vec.len());
2012                    facets_vec.push(FacetField {
2013                        name: schema_field.field.clone(),
2014                        values: IndexMap::new(),
2015                        min: ValueType::None,
2016                        max: ValueType::None,
2017                        offset: facets_size_sum,
2018                        field_type: schema_field.field_type.clone(),
2019                    });
2020                    facets_size_sum += facet_size;
2021                }
2022
2023                if schema_field.index_lexical || schema_field.index_vector {
2024                    indexed_field_vec.push(IndexedField {
2025                        schema_field_name: schema_field.field.clone(),
2026                        is_longest_field: false,
2027                        field_length_sum: 0,
2028                        indexed_field_id: indexed_field_vec.len(),
2029                    });
2030                    indexed_schema_vec.push(schema_field_clone);
2031                    document_length_compressed_array.push([0; ROARING_BLOCK_SIZE]);
2032                }
2033
2034                if schema_field.store {
2035                    stored_field_names.push(schema_field.field.clone());
2036                }
2037            }
2038
2039            if !facets_vec.is_empty()
2040                && let Ok(file) = File::open(Path::new(index_path).join(FACET_VALUES_FILENAME))
2041                && let Ok(facets) = serde_json::from_reader(BufReader::new(file))
2042            {
2043                let mut facets: Vec<FacetField> = facets;
2044                if facets_vec.len() == facets.len() {
2045                    for i in 0..facets.len() {
2046                        facets[i].offset = facets_vec[i].offset;
2047                        facets[i].field_type = facets_vec[i].field_type.clone();
2048                    }
2049                }
2050                facets_vec = facets;
2051            }
2052
2053            let synonyms_map = get_synonyms_map(synonyms, segment_number_mask1);
2054
2055            let shard_number = if let Some(shard_number) = force_shard_number {
2056                shard_number
2057            } else {
2058                cmp::min(
2059                    available_parallelism()
2060                        .map(|n| n.get())
2061                        .unwrap_or_else(|_| num_cpus::get_physical()),
2062                    num_cpus::get_physical(),
2063                )
2064            };
2065
2066            let (
2067                vector_dimensions,
2068                embedding_model_option,
2069                vector_precision,
2070                chunk_size,
2071                quantization,
2072                vector_similarity,
2073            ) = if is_vector_indexing {
2074                let (
2075                    dimensions,
2076                    model_path,
2077                    precision,
2078                    chunk_size,
2079                    quantization,
2080                    vector_similarity,
2081                ) = match &meta.inference {
2082                    Inference::Model2Vec {
2083                        model,
2084                        chunk_size,
2085                        quantization,
2086                    } => {
2087                        let chunk_size = *chunk_size.max(&10);
2088                        match model {
2089                            Model::PotionBase2M => (
2090                                0,
2091                                "minishlab/potion-base-2M",
2092                                Precision::F32,
2093                                chunk_size,
2094                                *quantization,
2095                                VectorSimilarity::Cosine,
2096                            ),
2097                            Model::PotionBase4M => (
2098                                0,
2099                                "minishlab/potion-base-4M",
2100                                Precision::F32,
2101                                chunk_size,
2102                                *quantization,
2103                                VectorSimilarity::Cosine,
2104                            ),
2105                            Model::PotionBase8M => (
2106                                0,
2107                                "minishlab/potion-base-8M",
2108                                Precision::F32,
2109                                chunk_size,
2110                                *quantization,
2111                                VectorSimilarity::Cosine,
2112                            ),
2113                            Model::PotionBase32M => (
2114                                0,
2115                                "minishlab/potion-base-32M",
2116                                Precision::F32,
2117                                chunk_size,
2118                                *quantization,
2119                                VectorSimilarity::Cosine,
2120                            ),
2121                            Model::PotionMultilingual128M => (
2122                                0,
2123                                "minishlab/potion-multilingual-128M",
2124                                Precision::F32,
2125                                chunk_size,
2126                                *quantization,
2127                                VectorSimilarity::Cosine,
2128                            ),
2129                            Model::PotionRetrieval32M => (
2130                                0,
2131                                "minishlab/potion-retrieval-32M",
2132                                Precision::F32,
2133                                chunk_size,
2134                                *quantization,
2135                                VectorSimilarity::Cosine,
2136                            ),
2137                            Model::PotionCode16MV2 => (
2138                                0,
2139                                "minishlab/potion-code-16M-v2",
2140                                Precision::F32,
2141                                chunk_size,
2142                                *quantization,
2143                                VectorSimilarity::Cosine,
2144                            ),
2145                        }
2146                    }
2147                    Inference::Model2VecCustom {
2148                        path,
2149                        chunk_size,
2150                        quantization,
2151                    } => (
2152                        0,
2153                        path.as_str(),
2154                        Precision::F32,
2155                        *chunk_size,
2156                        *quantization,
2157                        VectorSimilarity::Cosine,
2158                    ),
2159                    Inference::External {
2160                        dimensions: vector_dimensions,
2161                        precision: vector_precision,
2162                        quantization: vector_quantization,
2163                        similarity,
2164                    } => (
2165                        *vector_dimensions,
2166                        "",
2167                        *vector_precision,
2168                        0,
2169                        *vector_quantization,
2170                        *similarity,
2171                    ),
2172                    Inference::None => (
2173                        0,
2174                        "",
2175                        Precision::None,
2176                        0,
2177                        Quantization::None,
2178                        VectorSimilarity::Cosine,
2179                    ),
2180                };
2181
2182                if !model_path.is_empty() {
2183                    let model =
2184                        Some(StaticModel::from_pretrained(model_path, None, None, None).unwrap());
2185                    let dimensions = model.as_ref().unwrap().encode(&["test".to_string()])[0].len();
2186                    (
2187                        dimensions,
2188                        model,
2189                        precision,
2190                        chunk_size,
2191                        quantization,
2192                        VectorSimilarity::Cosine,
2193                    )
2194                } else {
2195                    (
2196                        dimensions,
2197                        None,
2198                        precision,
2199                        chunk_size,
2200                        quantization,
2201                        vector_similarity,
2202                    )
2203                }
2204            } else {
2205                (
2206                    0,
2207                    None,
2208                    Precision::None,
2209                    0,
2210                    Quantization::None,
2211                    VectorSimilarity::Cosine,
2212                )
2213            };
2214
2215            let turbo_quant = if quantization == Quantization::TurboQuantI8 {
2216                TurboQuant::new(vector_dimensions, 1234)
2217            } else {
2218                TurboQuant::new(0, 1234)
2219            };
2220
2221            let vector_dimensions_original = vector_dimensions;
2222            let vector_dimensions = if quantization == Quantization::TurboQuantI8
2223                && vector_precision == Precision::F32
2224            {
2225                TurboQuant::next_power_of_two(vector_dimensions)
2226            } else {
2227                vector_dimensions
2228            };
2229
2230            let mut shard_vec: Vec<Arc<RwLock<Shard>>> = Vec::new();
2231            if serialize_schema {
2232                let mut result_object_list = Vec::new();
2233                let index_path_clone = Arc::new(index_path.to_path_buf());
2234                for i in 0..shard_number {
2235                    let index_path_clone2 = index_path_clone.clone();
2236                    let meta_clone = meta.clone();
2237                    let schema_clone = schema.clone();
2238                    let turbo_quant_clone = turbo_quant.clone();
2239                    result_object_list.push(tokio::spawn(async move {
2240                        let shard_path = index_path_clone2.join("shards").join(i.to_string());
2241                        let mut shard_meta = meta_clone.clone();
2242                        shard_meta.id = i as u64;
2243
2244                        let mut shard = create_shard(
2245                            &shard_path,
2246                            &shard_meta,
2247                            &schema_clone,
2248                            serialize_schema,
2249                            &Vec::new(),
2250                            segment_number_bits1,
2251                            mute,
2252                            longest_field_id_option,
2253                        )
2254                        .unwrap();
2255                        shard.shard_number = shard_number;
2256                        shard.vector_dimensions = vector_dimensions;
2257                        shard.vector_dimensions_original = vector_dimensions_original;
2258                        shard.vector_precision = vector_precision;
2259                        shard.quantization = quantization;
2260                        shard.vector_similarity = vector_similarity;
2261                        shard.is_avx2 = *IS_AVX2;
2262                        shard.is_neon = *IS_NEON;
2263                        shard.is_simd = *IS_SIMD;
2264                        shard.chunk_size = chunk_size;
2265                        shard.turbo_quant = turbo_quant_clone;
2266
2267                        let shard_arc = Arc::new(RwLock::new(shard));
2268                        (shard_arc, i)
2269                    }));
2270                }
2271                for result_object_shard in result_object_list {
2272                    let ro_shard = result_object_shard.await.unwrap();
2273                    shard_vec.push(ro_shard.0);
2274                }
2275            }
2276
2277            let mut index = Index {
2278                docid_global: Arc::new(RwLock::new(0)),
2279                index_format_version_major: INDEX_FORMAT_VERSION_MAJOR,
2280                index_format_version_minor: INDEX_FORMAT_VERSION_MINOR,
2281
2282                index_file,
2283                index_path_string: index_path_string.to_owned(),
2284                stored_field_names,
2285
2286                compressed_index_segment_block_buffer: vec![0; 10_000_000],
2287                indexed_doc_count: 0,
2288                indexed_vector_count: 0,
2289                indexed_cluster_count: 0,
2290                deleted_doc_count: 0,
2291                segment_number1: 0,
2292                segment_number_mask1: 0,
2293                schema_map,
2294                indexed_field_vec,
2295                meta: meta.clone(),
2296                mute,
2297                facets: facets_vec,
2298                synonyms_map,
2299
2300                shard_number,
2301                shard_vec,
2302
2303                max_dictionary_entries: if let Some(spelling_correction) = &meta.spelling_correction
2304                {
2305                    spelling_correction.max_dictionary_entries
2306                } else {
2307                    usize::MAX
2308                },
2309
2310                symspell_option: if let Some(spelling_correction) = meta.spelling_correction {
2311                    Some(Arc::new(RwLock::new(SymSpell::new(
2312                        spelling_correction.max_dictionary_edit_distance,
2313                        spelling_correction.term_length_threshold,
2314                        7,
2315                        spelling_correction.count_threshold,
2316                    ))))
2317                } else {
2318                    None
2319                },
2320
2321                max_completion_entries: if let Some(query_completion) = &meta.query_completion {
2322                    query_completion.max_completion_entries
2323                } else {
2324                    usize::MAX
2325                },
2326
2327                completion_option: meta
2328                    .query_completion
2329                    .as_ref()
2330                    .map(|_query_completion| Arc::new(RwLock::new(PruningRadixTrie::new()))),
2331
2332                frequent_hashset,
2333
2334                embedding_model_option,
2335                vector_dimensions,
2336                vector_dimensions_original,
2337                vector_precision,
2338                quantization,
2339                vector_similarity,
2340                is_avx2: *IS_AVX2,
2341                is_neon: *IS_NEON,
2342                is_simd: *IS_SIMD,
2343                is_vector_indexing,
2344                is_lexical_indexing,
2345                chunk_size,
2346                turbo_quant,
2347            };
2348
2349            let file_len = index.index_file.metadata().unwrap().len();
2350            if file_len == 0 {
2351                write_u16(
2352                    INDEX_FORMAT_VERSION_MAJOR,
2353                    &mut index.compressed_index_segment_block_buffer,
2354                    0,
2355                );
2356                write_u16(
2357                    INDEX_FORMAT_VERSION_MINOR,
2358                    &mut index.compressed_index_segment_block_buffer,
2359                    2,
2360                );
2361                let _ = index.index_file.write(
2362                    &index.compressed_index_segment_block_buffer[0..INDEX_HEADER_SIZE as usize],
2363                );
2364            } else {
2365                let _ = index.index_file.read(
2366                    &mut index.compressed_index_segment_block_buffer[0..INDEX_HEADER_SIZE as usize],
2367                );
2368                index.index_format_version_major =
2369                    read_u16(&index.compressed_index_segment_block_buffer, 0);
2370                index.index_format_version_minor =
2371                    read_u16(&index.compressed_index_segment_block_buffer, 2);
2372
2373                if INDEX_FORMAT_VERSION_MAJOR != index.index_format_version_major {
2374                    return Err("incompatible index format version ".to_string()
2375                        + &INDEX_FORMAT_VERSION_MAJOR.to_string()
2376                        + " "
2377                        + &index.index_format_version_major.to_string());
2378                };
2379
2380                if index.index_format_version_major == 6 && index.index_format_version_minor == 0 {
2381                    index.meta.document_compression = DocumentCompression::Zstd;
2382                }
2383            }
2384
2385            index.segment_number1 = segment_number1;
2386            index.segment_number_mask1 = segment_number_mask1;
2387
2388            if serialize_schema {
2389                serde_json::to_writer(
2390                    &File::create(Path::new(index_path).join(SCHEMA_FILENAME)).unwrap(),
2391                    &schema,
2392                )
2393                .unwrap();
2394
2395                if !synonyms.is_empty() {
2396                    serde_json::to_writer(
2397                        &File::create(Path::new(index_path).join(SYNONYMS_FILENAME)).unwrap(),
2398                        &synonyms,
2399                    )
2400                    .unwrap();
2401                }
2402
2403                serde_json::to_writer(
2404                    &File::create(Path::new(index_path).join(META_FILENAME)).unwrap(),
2405                    &index.meta,
2406                )
2407                .unwrap();
2408            }
2409
2410            let index_arc = Arc::new(RwLock::new(index));
2411
2412            if serialize_schema {
2413                for shard in index_arc.write().await.shard_vec.iter() {
2414                    shard.write().await.index_option = Some(index_arc.clone());
2415                }
2416            }
2417
2418            Ok(index_arc)
2419        }
2420        Err(e) => {
2421            println!("file opening error");
2422            Err(e.to_string())
2423        }
2424    }
2425}
2426
2427#[allow(clippy::too_many_arguments)]
2428pub(crate) fn create_shard(
2429    index_path: &Path,
2430    meta: &IndexMetaObject,
2431    schema: &Vec<SchemaField>,
2432    serialize_schema: bool,
2433    synonyms: &Vec<Synonym>,
2434    segment_number_bits1: usize,
2435    mute: bool,
2436    longest_field_id_option: Option<usize>,
2437) -> Result<Shard, String> {
2438    let segment_number1 = 1usize << segment_number_bits1;
2439    let segment_number_mask1 = (1u32 << segment_number_bits1) - 1;
2440
2441    let index_path_buf = index_path.to_path_buf();
2442    let index_path_string = index_path_buf.to_str().unwrap();
2443
2444    if !index_path.exists() {
2445        fs::create_dir_all(index_path).unwrap();
2446    }
2447
2448    let file_path = Path::new(index_path_string).join(FILE_PATH);
2449    if !file_path.exists() {
2450        fs::create_dir_all(file_path).unwrap();
2451    }
2452
2453    match File::options()
2454        .read(true)
2455        .write(true)
2456        .create(true)
2457        .truncate(false)
2458        .open(Path::new(index_path).join(INDEX_FILENAME))
2459    {
2460        Ok(index_file) => {
2461            let docstore_file = File::options()
2462                .read(true)
2463                .write(true)
2464                .create(true)
2465                .truncate(false)
2466                .open(Path::new(index_path).join(DOCSTORE_FILENAME))
2467                .unwrap();
2468
2469            let delete_file = File::options()
2470                .read(true)
2471                .write(true)
2472                .create(true)
2473                .truncate(false)
2474                .open(Path::new(index_path).join(DELETE_FILENAME))
2475                .unwrap();
2476
2477            let facets_file = File::options()
2478                .read(true)
2479                .write(true)
2480                .create(true)
2481                .truncate(false)
2482                .open(Path::new(index_path).join(FACET_FILENAME))
2483                .unwrap();
2484
2485            let vector_file = File::options()
2486                .read(true)
2487                .write(true)
2488                .create(true)
2489                .truncate(false)
2490                .open(Path::new(index_path).join(VECTOR_FILENAME))
2491                .unwrap();
2492
2493            let mut document_length_compressed_array: Vec<[u8; ROARING_BLOCK_SIZE]> = Vec::new();
2494            let mut indexed_field_vec: Vec<IndexedField> = Vec::new();
2495            let mut facets_vec: Vec<FacetField> = Vec::new();
2496            let mut facets_map: AHashMap<String, usize> = AHashMap::new();
2497
2498            let mut schema_map: HashMap<String, SchemaField> = HashMap::new();
2499            let mut indexed_schema_vec: Vec<SchemaField> = Vec::new();
2500            let mut stored_fields_flag = false;
2501            let mut is_vector_indexing = false;
2502            let mut is_lexical_indexing = false;
2503            let mut stored_field_names = Vec::new();
2504            let mut facets_size_sum = 0;
2505            for (i, schema_field) in schema.iter().enumerate() {
2506                if schema_field.index_vector {
2507                    is_vector_indexing = true;
2508                }
2509                if schema_field.index_lexical {
2510                    is_lexical_indexing = true;
2511                }
2512                let mut schema_field_clone = schema_field.clone();
2513
2514                schema_field_clone.indexed_field_id = indexed_field_vec.len();
2515
2516                schema_field_clone.field_id = i;
2517                schema_map.insert(schema_field.field.clone(), schema_field_clone.clone());
2518
2519                if schema_field.facet {
2520                    let facet_size = match schema_field.field_type {
2521                        FieldType::U8 => 1,
2522                        FieldType::U16 => 2,
2523                        FieldType::U32 => 4,
2524                        FieldType::U64 => 8,
2525                        FieldType::I8 => 1,
2526                        FieldType::I16 => 2,
2527                        FieldType::I32 => 4,
2528                        FieldType::I64 => 8,
2529                        FieldType::Timestamp => 8,
2530                        FieldType::F32 => 4,
2531                        FieldType::F64 => 8,
2532                        FieldType::String16 => 2,
2533                        FieldType::String32 => 4,
2534                        FieldType::StringSet16 => 2,
2535                        FieldType::StringSet32 => 4,
2536                        FieldType::Point => 8,
2537                        _ => 1,
2538                    };
2539
2540                    facets_map.insert(schema_field.field.clone(), facets_vec.len());
2541                    facets_vec.push(FacetField {
2542                        name: schema_field.field.clone(),
2543                        values: IndexMap::new(),
2544                        min: ValueType::None,
2545                        max: ValueType::None,
2546                        offset: facets_size_sum,
2547                        field_type: schema_field.field_type.clone(),
2548                    });
2549                    facets_size_sum += facet_size;
2550                }
2551
2552                if schema_field.index_lexical || schema_field.index_vector {
2553                    indexed_field_vec.push(IndexedField {
2554                        schema_field_name: schema_field.field.clone(),
2555                        is_longest_field: false,
2556                        field_length_sum: 0,
2557                        indexed_field_id: indexed_field_vec.len(),
2558                    });
2559                    indexed_schema_vec.push(schema_field_clone);
2560                    document_length_compressed_array.push([0; ROARING_BLOCK_SIZE]);
2561                }
2562
2563                if schema_field.store {
2564                    stored_fields_flag = true;
2565                    stored_field_names.push(schema_field.field.clone());
2566                }
2567            }
2568
2569            let indexed_field_id_bits =
2570                (usize::BITS - (indexed_field_vec.len() - 1).leading_zeros()) as usize;
2571
2572            let index_file_mmap;
2573            let docstore_file_mmap = if meta.access_type == AccessType::Mmap {
2574                index_file_mmap = unsafe { Mmap::map(&index_file).expect("Unable to create Mmap") };
2575                unsafe { Mmap::map(&docstore_file).expect("Unable to create Mmap") }
2576            } else {
2577                index_file_mmap = unsafe {
2578                    MmapOptions::new()
2579                        .len(0)
2580                        .map(&index_file)
2581                        .expect("Unable to create Mmap")
2582                };
2583                unsafe {
2584                    MmapOptions::new()
2585                        .len(0)
2586                        .map(&docstore_file)
2587                        .expect("Unable to create Mmap")
2588                }
2589            };
2590
2591            if !facets_vec.is_empty()
2592                && let Ok(file) = File::open(Path::new(index_path).join(FACET_VALUES_FILENAME))
2593                && let Ok(facets) = serde_json::from_reader(BufReader::new(file))
2594            {
2595                let mut facets: Vec<FacetField> = facets;
2596                if facets_vec.len() == facets.len() {
2597                    for i in 0..facets.len() {
2598                        facets[i].offset = facets_vec[i].offset;
2599                        facets[i].field_type = facets_vec[i].field_type.clone();
2600                    }
2601                }
2602                facets_vec = facets;
2603            }
2604
2605            let facets_file_mmap = if !facets_vec.is_empty() {
2606                if facets_file.metadata().unwrap().len() == 0 {
2607                    facets_file
2608                        .set_len((facets_size_sum * ROARING_BLOCK_SIZE) as u64)
2609                        .expect("Unable to set len");
2610                }
2611
2612                unsafe { MmapMut::map_mut(&facets_file).expect("Unable to create Mmap") }
2613            } else {
2614                unsafe { MmapMut::map_mut(&facets_file).expect("Unable to create Mmap") }
2615            };
2616
2617            let vector_file_mmap =
2618                unsafe { Mmap::map(&vector_file).expect("Unable to create Mmap") };
2619
2620            let synonyms_map = get_synonyms_map(synonyms, segment_number_mask1);
2621
2622            let facets_len = facets_vec.len();
2623
2624            #[cfg(feature = "zh")]
2625            let word_segmentation_option = if meta.tokenizer == TokenizerType::UnicodeAlphanumericZH
2626            {
2627                let mut word_segmentation = WordSegmentationTM::new();
2628                word_segmentation.load_dictionary(0, 1, true);
2629                Some(word_segmentation)
2630            } else {
2631                None
2632            };
2633
2634            let shard_number = 1;
2635
2636            let stemmer = match meta.stemmer {
2637                StemmerType::Arabic => Some(Stemmer::create(Algorithm::Arabic)),
2638                StemmerType::Armenian => Some(Stemmer::create(Algorithm::Armenian)),
2639                StemmerType::Basque => Some(Stemmer::create(Algorithm::Basque)),
2640                StemmerType::Catalan => Some(Stemmer::create(Algorithm::Catalan)),
2641                StemmerType::Czech => Some(Stemmer::create(Algorithm::Czech)),
2642                StemmerType::Danish => Some(Stemmer::create(Algorithm::Danish)),
2643                StemmerType::Dutch => Some(Stemmer::create(Algorithm::Dutch)),
2644                StemmerType::DutchPorter => Some(Stemmer::create(Algorithm::DutchPorter)),
2645                StemmerType::English => Some(Stemmer::create(Algorithm::English)),
2646                StemmerType::Esperanto => Some(Stemmer::create(Algorithm::Esperanto)),
2647                StemmerType::Estonian => Some(Stemmer::create(Algorithm::Estonian)),
2648                StemmerType::Finnish => Some(Stemmer::create(Algorithm::Finnish)),
2649                StemmerType::French => Some(Stemmer::create(Algorithm::French)),
2650                StemmerType::German => Some(Stemmer::create(Algorithm::German)),
2651                StemmerType::Greek => Some(Stemmer::create(Algorithm::Greek)),
2652                StemmerType::Hindi => Some(Stemmer::create(Algorithm::Hindi)),
2653                StemmerType::Hungarian => Some(Stemmer::create(Algorithm::Hungarian)),
2654                StemmerType::Indonesian => Some(Stemmer::create(Algorithm::Indonesian)),
2655                StemmerType::Irish => Some(Stemmer::create(Algorithm::Irish)),
2656                StemmerType::Italian => Some(Stemmer::create(Algorithm::Italian)),
2657                StemmerType::Lithuanian => Some(Stemmer::create(Algorithm::Lithuanian)),
2658                StemmerType::Lovins => Some(Stemmer::create(Algorithm::Lovins)),
2659                StemmerType::Nepali => Some(Stemmer::create(Algorithm::Nepali)),
2660                StemmerType::Norwegian => Some(Stemmer::create(Algorithm::Norwegian)),
2661                StemmerType::Persian => Some(Stemmer::create(Algorithm::Persian)),
2662                StemmerType::Polish => Some(Stemmer::create(Algorithm::Polish)),
2663                StemmerType::Porter => Some(Stemmer::create(Algorithm::Porter)),
2664                StemmerType::Portuguese => Some(Stemmer::create(Algorithm::Portuguese)),
2665                StemmerType::Romanian => Some(Stemmer::create(Algorithm::Romanian)),
2666                StemmerType::Russian => Some(Stemmer::create(Algorithm::Russian)),
2667                StemmerType::Serbian => Some(Stemmer::create(Algorithm::Serbian)),
2668                StemmerType::Sesotho => Some(Stemmer::create(Algorithm::Sesotho)),
2669                StemmerType::Spanish => Some(Stemmer::create(Algorithm::Spanish)),
2670                StemmerType::Swedish => Some(Stemmer::create(Algorithm::Swedish)),
2671                StemmerType::Tamil => Some(Stemmer::create(Algorithm::Tamil)),
2672                StemmerType::Turkish => Some(Stemmer::create(Algorithm::Turkish)),
2673                StemmerType::Ukrainian => Some(Stemmer::create(Algorithm::Ukrainian)),
2674                StemmerType::Yiddish => Some(Stemmer::create(Algorithm::Yiddish)),
2675                _ => None,
2676            };
2677
2678            let stop_words: AHashSet<String> = match &meta.stop_words {
2679                StopwordType::None => AHashSet::new(),
2680                StopwordType::English => FREQUENT_EN.lines().map(|x| x.to_string()).collect(),
2681                StopwordType::German => FREQUENT_DE.lines().map(|x| x.to_string()).collect(),
2682                StopwordType::French => FREQUENT_FR.lines().map(|x| x.to_string()).collect(),
2683                StopwordType::Spanish => FREQUENT_ES.lines().map(|x| x.to_string()).collect(),
2684                StopwordType::Custom { terms } => terms.iter().map(|x| x.to_string()).collect(),
2685            };
2686
2687            let frequent_words: Vec<String> = match &meta.frequent_words {
2688                FrequentwordType::None => Vec::new(),
2689                FrequentwordType::English => {
2690                    let mut words: Vec<String> =
2691                        FREQUENT_EN.lines().map(|x| x.to_string()).collect();
2692                    words.sort_unstable();
2693                    words
2694                }
2695                FrequentwordType::German => {
2696                    let mut words: Vec<String> =
2697                        FREQUENT_DE.lines().map(|x| x.to_string()).collect();
2698                    words.sort_unstable();
2699                    words
2700                }
2701                FrequentwordType::French => {
2702                    let mut words: Vec<String> =
2703                        FREQUENT_FR.lines().map(|x| x.to_string()).collect();
2704                    words.sort_unstable();
2705                    words
2706                }
2707                FrequentwordType::Spanish => {
2708                    let mut words: Vec<String> =
2709                        FREQUENT_ES.lines().map(|x| x.to_string()).collect();
2710                    words.sort_unstable();
2711                    words
2712                }
2713                FrequentwordType::Custom { terms } => {
2714                    let mut words: Vec<String> = terms.iter().map(|x| x.to_string()).collect();
2715                    words.sort_unstable();
2716                    words
2717                }
2718            };
2719
2720            let frequent_hashset: AHashSet<u64> = frequent_words
2721                .iter()
2722                .map(|x| hash64(x.as_bytes()))
2723                .collect();
2724
2725            let mut index = Shard {
2726                semaphore: Arc::new(Semaphore::new(1)),
2727
2728                index_format_version_major: INDEX_FORMAT_VERSION_MAJOR,
2729                index_format_version_minor: INDEX_FORMAT_VERSION_MINOR,
2730                docstore_file,
2731                delete_file,
2732                delete_hashset: AHashSet::new(),
2733                index_file,
2734                index_path_string: index_path_string.to_owned(),
2735                index_file_mmap,
2736                docstore_file_mmap,
2737                stored_field_names,
2738                compressed_index_segment_block_buffer: vec![0; 10_000_000],
2739                compressed_docstore_segment_block_buffer: if stored_fields_flag {
2740                    vec![0; ROARING_BLOCK_SIZE * 4]
2741                } else {
2742                    Vec::new()
2743                },
2744                document_length_normalized_average: 0.0,
2745                indexed_doc_count: 0,
2746                committed_doc_count: 0,
2747                is_last_level_incomplete: false,
2748                last_level_index_file_start_pos: 0,
2749                last_level_docstore_file_start_pos: 0,
2750                last_level_vector_file_start_pos: 0,
2751                positions_sum_normalized: 0,
2752                segment_number1: 0,
2753                segment_number_bits1,
2754                segment_number_mask1: 0,
2755                level_index: Vec::new(),
2756                segments_index: Vec::new(),
2757                segments_level0: Vec::new(),
2758
2759                uncommitted: false,
2760                modified: false,
2761                enable_fallback: false,
2762                enable_single_term_topk: false,
2763                enable_search_quality_test: false,
2764                enable_inter_query_threading: false,
2765                enable_inter_query_threading_auto: false,
2766                schema_map,
2767                indexed_field_id_bits,
2768                indexed_field_id_mask: (1usize << indexed_field_id_bits) - 1,
2769                longest_field_id: longest_field_id_option.unwrap_or_default(),
2770                longest_field_auto: longest_field_id_option.is_none(),
2771                indexed_field_vec,
2772                indexed_schema_vec,
2773                meta: meta.clone(),
2774                document_length_compressed_array,
2775                key_count_sum: 0,
2776
2777                block_id: 0,
2778                strip_compressed_sum: 0,
2779                postings_buffer: vec![0; POSTING_BUFFER_SIZE],
2780                postings_buffer_pointer: 0,
2781
2782                docid_count: 0,
2783                size_compressed_docid_index: 0,
2784                size_compressed_positions_index: 0,
2785                position_count: 0,
2786                postinglist_count: 0,
2787                mute,
2788                frequentword_results: AHashMap::new(),
2789                facets: facets_vec,
2790                facets_map,
2791                facets_size_sum,
2792                facets_file,
2793                facets_file_mmap,
2794                string_set_to_single_term_id_vec: vec![AHashMap::new(); facets_len],
2795                bm25_component_cache: [0.0; 256],
2796                synonyms_map,
2797                #[cfg(feature = "zh")]
2798                word_segmentation_option,
2799
2800                shard_number,
2801                index_option: None,
2802                stemmer,
2803                stop_words,
2804                frequent_words,
2805                frequent_hashset,
2806                key_head_size: if meta.ngram_indexing == 0 {
2807                    20
2808                } else if meta.ngram_indexing < 8 {
2809                    22
2810                } else {
2811                    23
2812                },
2813                level_terms: AHashMap::new(),
2814                level_completions: Arc::new(RwLock::new(AHashMap::with_capacity(200_000))),
2815
2816                chunks_meta: Vec::new(),
2817                chunks_string: Vec::new(),
2818                vector_file,
2819                vector_file_mmap,
2820                indexed_vector_count: 0,
2821                indexed_cluster_count: 0,
2822                is_vector_indexing,
2823                is_lexical_indexing,
2824                block_vector_buffer: Vec::new(),
2825                vector_dimensions: 0,
2826                vector_dimensions_original: 0,
2827                vector_precision: Precision::None,
2828                quantization: Quantization::None,
2829                vector_similarity: VectorSimilarity::Dot,
2830                is_avx2: false,
2831                is_neon: false,
2832                is_simd: false,
2833                chunk_size: 0,
2834                min_vector_value: f32::MAX,
2835                max_vector_value: f32::MIN,
2836                turbo_quant: TurboQuant::new(0, 1234),
2837            };
2838
2839            let file_len = index.index_file.metadata().unwrap().len();
2840            if file_len == 0 {
2841                write_u16(
2842                    INDEX_FORMAT_VERSION_MAJOR,
2843                    &mut index.compressed_index_segment_block_buffer,
2844                    0,
2845                );
2846                write_u16(
2847                    INDEX_FORMAT_VERSION_MINOR,
2848                    &mut index.compressed_index_segment_block_buffer,
2849                    2,
2850                );
2851                let _ = index.index_file.write(
2852                    &index.compressed_index_segment_block_buffer[0..INDEX_HEADER_SIZE as usize],
2853                );
2854            } else {
2855                let _ = index.index_file.read(
2856                    &mut index.compressed_index_segment_block_buffer[0..INDEX_HEADER_SIZE as usize],
2857                );
2858                index.index_format_version_major =
2859                    read_u16(&index.compressed_index_segment_block_buffer, 0);
2860                index.index_format_version_minor =
2861                    read_u16(&index.compressed_index_segment_block_buffer, 2);
2862
2863                if INDEX_FORMAT_VERSION_MAJOR != index.index_format_version_major {
2864                    return Err("incompatible index format version ".to_string()
2865                        + &INDEX_FORMAT_VERSION_MAJOR.to_string()
2866                        + " "
2867                        + &index.index_format_version_major.to_string());
2868                };
2869
2870                if index.index_format_version_major == 6 && index.index_format_version_minor == 0 {
2871                    index.meta.document_compression = DocumentCompression::Zstd;
2872                }
2873            }
2874
2875            index.segment_number1 = segment_number1;
2876            index.segment_number_mask1 = segment_number_mask1;
2877            index.segments_level0 = vec![
2878                SegmentLevel0 {
2879                    segment: AHashMap::with_capacity(SEGMENT_KEY_CAPACITY),
2880                    ..Default::default()
2881                };
2882                index.segment_number1
2883            ];
2884
2885            index.segments_index = Vec::new();
2886            for _i in 0..index.segment_number1 {
2887                index.segments_index.push(SegmentIndex {
2888                    byte_array_blocks: Vec::new(),
2889                    byte_array_blocks_pointer: Vec::new(),
2890                    segment: AHashMap::new(),
2891                });
2892            }
2893
2894            if serialize_schema {
2895                serde_json::to_writer(
2896                    &File::create(Path::new(index_path).join(SCHEMA_FILENAME)).unwrap(),
2897                    &schema,
2898                )
2899                .unwrap();
2900
2901                if !synonyms.is_empty() {
2902                    serde_json::to_writer(
2903                        &File::create(Path::new(index_path).join(SYNONYMS_FILENAME)).unwrap(),
2904                        &synonyms,
2905                    )
2906                    .unwrap();
2907                }
2908
2909                serde_json::to_writer(
2910                    &File::create(Path::new(index_path).join(META_FILENAME)).unwrap(),
2911                    &index.meta,
2912                )
2913                .unwrap();
2914            }
2915
2916            Ok(index)
2917        }
2918        Err(e) => {
2919            println!("file opening error");
2920            Err(e.to_string())
2921        }
2922    }
2923}
2924
2925#[inline(always)]
2926pub(crate) fn get_document_length_compressed_mmap(
2927    index: &Shard,
2928    field_id: usize,
2929    block_id: usize,
2930    doc_id_block: usize,
2931) -> u8 {
2932    index.index_file_mmap[index.level_index[block_id].document_length_compressed_array_pointer
2933        + (field_id << 16)
2934        + doc_id_block]
2935}
2936
2937#[allow(clippy::too_many_arguments)]
2938pub(crate) fn get_max_score(
2939    index: &Shard,
2940    segment: &SegmentIndex,
2941    posting_count_ngram_1: u32,
2942    posting_count_ngram_2: u32,
2943    posting_count_ngram_3: u32,
2944    posting_count: u32,
2945    block_id: usize,
2946    max_docid: usize,
2947    max_p_docid: usize,
2948    pointer_pivot_p_docid: usize,
2949    compression_type_pointer: u32,
2950    ngram_type: &NgramType,
2951) -> f32 {
2952    let byte_array = if index.meta.access_type == AccessType::Mmap {
2953        &index.index_file_mmap[segment.byte_array_blocks_pointer[block_id].0
2954            ..segment.byte_array_blocks_pointer[block_id].0
2955                + segment.byte_array_blocks_pointer[block_id].1]
2956    } else {
2957        &segment.byte_array_blocks[block_id]
2958    };
2959
2960    let mut bm25f = 0.0;
2961
2962    let rank_position_pointer_range: u32 =
2963        compression_type_pointer & 0b0011_1111_1111_1111_1111_1111_1111_1111;
2964
2965    let posting_pointer_size_sum;
2966    let rank_position_pointer;
2967    let posting_pointer_size;
2968    let embed_flag;
2969    if max_p_docid < pointer_pivot_p_docid {
2970        posting_pointer_size_sum = max_p_docid as u32 * 2;
2971        rank_position_pointer = read_u16(
2972            byte_array,
2973            rank_position_pointer_range as usize + posting_pointer_size_sum as usize,
2974        ) as u32;
2975        posting_pointer_size = 2;
2976        embed_flag = (rank_position_pointer & 0b10000000_00000000) != 0;
2977    } else {
2978        posting_pointer_size_sum = (max_p_docid as u32) * 3 - pointer_pivot_p_docid as u32;
2979        rank_position_pointer = read_u32(
2980            byte_array,
2981            rank_position_pointer_range as usize + posting_pointer_size_sum as usize,
2982        );
2983        posting_pointer_size = 3;
2984        embed_flag = (rank_position_pointer & 0b10000000_00000000_00000000) != 0;
2985    };
2986
2987    let positions_pointer = if embed_flag {
2988        rank_position_pointer_range as usize + posting_pointer_size_sum as usize
2989    } else {
2990        let pointer_value = if posting_pointer_size == 2 {
2991            rank_position_pointer & 0b01111111_11111111
2992        } else {
2993            rank_position_pointer & 0b01111111_11111111_11111111
2994        } as usize;
2995
2996        rank_position_pointer_range as usize - pointer_value
2997    };
2998
2999    let mut field_vec: SmallVec<[(u16, usize); 2]> = SmallVec::new();
3000    let mut field_vec_ngram1 = SmallVec::new();
3001    let mut field_vec_ngram2 = SmallVec::new();
3002    let mut field_vec_ngram3 = SmallVec::new();
3003
3004    decode_positions_commit(
3005        posting_pointer_size,
3006        embed_flag,
3007        byte_array,
3008        positions_pointer,
3009        ngram_type,
3010        index.indexed_field_vec.len(),
3011        index.indexed_field_id_bits,
3012        index.indexed_field_id_mask,
3013        index.longest_field_id as u16,
3014        &mut field_vec,
3015        &mut field_vec_ngram1,
3016        &mut field_vec_ngram2,
3017        &mut field_vec_ngram3,
3018    );
3019
3020    if ngram_type == &NgramType::SingleTerm
3021        || index.meta.lexical_similarity == LexicalSimilarity::Bm25fProximity
3022    {
3023        let idf = (((index.indexed_doc_count as f32 - posting_count as f32 + 0.5)
3024            / (posting_count as f32 + 0.5))
3025            + 1.0)
3026            .ln();
3027
3028        for field in field_vec.iter() {
3029            let document_length_normalized = DOCUMENT_LENGTH_COMPRESSION[if index.meta.access_type
3030                == AccessType::Mmap
3031            {
3032                get_document_length_compressed_mmap(index, field.0 as usize, block_id, max_docid)
3033            } else {
3034                index.level_index[block_id].document_length_compressed_array[field.0 as usize]
3035                    [max_docid]
3036            } as usize] as f32;
3037
3038            let document_length_quotient =
3039                document_length_normalized / index.document_length_normalized_average;
3040
3041            let tf = field.1 as f32;
3042
3043            let weight = index.indexed_schema_vec[field.0 as usize].boost;
3044
3045            bm25f += weight
3046                * idf
3047                * ((tf * (K + 1.0) / (tf + (K * (1.0 - B + (B * document_length_quotient)))))
3048                    + SIGMA);
3049        }
3050    } else if ngram_type == &NgramType::NgramFF
3051        || ngram_type == &NgramType::NgramFR
3052        || ngram_type == &NgramType::NgramRF
3053    {
3054        let idf_ngram1 = (((index.indexed_doc_count as f32 - posting_count_ngram_1 as f32 + 0.5)
3055            / (posting_count_ngram_1 as f32 + 0.5))
3056            + 1.0)
3057            .ln();
3058
3059        let idf_ngram2 = (((index.indexed_doc_count as f32 - posting_count_ngram_2 as f32 + 0.5)
3060            / (posting_count_ngram_2 as f32 + 0.5))
3061            + 1.0)
3062            .ln();
3063
3064        for field in field_vec_ngram1.iter() {
3065            let document_length_normalized = DOCUMENT_LENGTH_COMPRESSION[if index.meta.access_type
3066                == AccessType::Mmap
3067            {
3068                get_document_length_compressed_mmap(index, field.0 as usize, block_id, max_docid)
3069            } else {
3070                index.level_index[block_id].document_length_compressed_array[field.0 as usize]
3071                    [max_docid]
3072            } as usize] as f32;
3073
3074            let document_length_quotient =
3075                document_length_normalized / index.document_length_normalized_average;
3076
3077            let tf_ngram1 = field.1 as f32;
3078
3079            let weight = index.indexed_schema_vec[field.0 as usize].boost;
3080
3081            bm25f += weight
3082                * idf_ngram1
3083                * ((tf_ngram1 * (K + 1.0)
3084                    / (tf_ngram1 + (K * (1.0 - B + (B * document_length_quotient)))))
3085                    + SIGMA);
3086        }
3087
3088        for field in field_vec_ngram2.iter() {
3089            let document_length_normalized = DOCUMENT_LENGTH_COMPRESSION[if index.meta.access_type
3090                == AccessType::Mmap
3091            {
3092                get_document_length_compressed_mmap(index, field.0 as usize, block_id, max_docid)
3093            } else {
3094                index.level_index[block_id].document_length_compressed_array[field.0 as usize]
3095                    [max_docid]
3096            } as usize] as f32;
3097
3098            let document_length_quotient =
3099                document_length_normalized / index.document_length_normalized_average;
3100
3101            let tf_ngram2 = field.1 as f32;
3102
3103            let weight = index.indexed_schema_vec[field.0 as usize].boost;
3104
3105            bm25f += weight
3106                * idf_ngram2
3107                * ((tf_ngram2 * (K + 1.0)
3108                    / (tf_ngram2 + (K * (1.0 - B + (B * document_length_quotient)))))
3109                    + SIGMA);
3110        }
3111    } else {
3112        let idf_ngram1 = (((index.indexed_doc_count as f32 - posting_count_ngram_1 as f32 + 0.5)
3113            / (posting_count_ngram_1 as f32 + 0.5))
3114            + 1.0)
3115            .ln();
3116
3117        let idf_ngram2 = (((index.indexed_doc_count as f32 - posting_count_ngram_2 as f32 + 0.5)
3118            / (posting_count_ngram_2 as f32 + 0.5))
3119            + 1.0)
3120            .ln();
3121
3122        let idf_ngram3 = (((index.indexed_doc_count as f32 - posting_count_ngram_3 as f32 + 0.5)
3123            / (posting_count_ngram_3 as f32 + 0.5))
3124            + 1.0)
3125            .ln();
3126
3127        for field in field_vec_ngram1.iter() {
3128            let document_length_normalized = DOCUMENT_LENGTH_COMPRESSION[if index.meta.access_type
3129                == AccessType::Mmap
3130            {
3131                get_document_length_compressed_mmap(index, field.0 as usize, block_id, max_docid)
3132            } else {
3133                index.level_index[block_id].document_length_compressed_array[field.0 as usize]
3134                    [max_docid]
3135            } as usize] as f32;
3136
3137            let document_length_quotient =
3138                document_length_normalized / index.document_length_normalized_average;
3139
3140            let tf_ngram1 = field.1 as f32;
3141
3142            let weight = index.indexed_schema_vec[field.0 as usize].boost;
3143
3144            bm25f += weight
3145                * idf_ngram1
3146                * ((tf_ngram1 * (K + 1.0)
3147                    / (tf_ngram1 + (K * (1.0 - B + (B * document_length_quotient)))))
3148                    + SIGMA);
3149        }
3150
3151        for field in field_vec_ngram2.iter() {
3152            let document_length_normalized = DOCUMENT_LENGTH_COMPRESSION[if index.meta.access_type
3153                == AccessType::Mmap
3154            {
3155                get_document_length_compressed_mmap(index, field.0 as usize, block_id, max_docid)
3156            } else {
3157                index.level_index[block_id].document_length_compressed_array[field.0 as usize]
3158                    [max_docid]
3159            } as usize] as f32;
3160
3161            let document_length_quotient =
3162                document_length_normalized / index.document_length_normalized_average;
3163
3164            let tf_ngram2 = field.1 as f32;
3165
3166            let weight = index.indexed_schema_vec[field.0 as usize].boost;
3167
3168            bm25f += weight
3169                * idf_ngram2
3170                * ((tf_ngram2 * (K + 1.0)
3171                    / (tf_ngram2 + (K * (1.0 - B + (B * document_length_quotient)))))
3172                    + SIGMA);
3173        }
3174
3175        for field in field_vec_ngram3.iter() {
3176            let document_length_normalized = DOCUMENT_LENGTH_COMPRESSION[if index.meta.access_type
3177                == AccessType::Mmap
3178            {
3179                get_document_length_compressed_mmap(index, field.0 as usize, block_id, max_docid)
3180            } else {
3181                index.level_index[block_id].document_length_compressed_array[field.0 as usize]
3182                    [max_docid]
3183            } as usize] as f32;
3184
3185            let document_length_quotient =
3186                document_length_normalized / index.document_length_normalized_average;
3187
3188            let tf_ngram3 = field.1 as f32;
3189
3190            let weight = index.indexed_schema_vec[field.0 as usize].boost;
3191
3192            bm25f += weight
3193                * idf_ngram3
3194                * ((tf_ngram3 * (K + 1.0)
3195                    / (tf_ngram3 + (K * (1.0 - B + (B * document_length_quotient)))))
3196                    + SIGMA);
3197        }
3198    }
3199    bm25f
3200}
3201
3202pub(crate) fn update_list_max_impact_score(index: &mut Shard) {
3203    if index.meta.access_type == AccessType::Mmap {
3204        return;
3205    }
3206
3207    for key0 in 0..index.segment_number1 {
3208        let keys: Vec<u64> = index.segments_index[key0].segment.keys().cloned().collect();
3209        for key in keys {
3210            let ngram_type = FromPrimitive::from_u64(key & 0b111).unwrap_or(NgramType::SingleTerm);
3211
3212            let blocks_len = index.segments_index[key0].segment[&key].blocks.len();
3213            let mut max_list_score = 0.0;
3214            for block_index in 0..blocks_len {
3215                let segment = &index.segments_index[key0];
3216                let posting_list = &segment.segment[&key];
3217                let block = &posting_list.blocks[block_index];
3218                let max_block_score = get_max_score(
3219                    index,
3220                    segment,
3221                    posting_list.posting_count_ngram_1,
3222                    posting_list.posting_count_ngram_2,
3223                    posting_list.posting_count_ngram_3,
3224                    posting_list.posting_count,
3225                    block.block_id as usize,
3226                    block.max_docid as usize,
3227                    block.max_p_docid as usize,
3228                    block.pointer_pivot_p_docid as usize,
3229                    block.compression_type_pointer,
3230                    &ngram_type,
3231                );
3232
3233                index.segments_index[key0]
3234                    .segment
3235                    .get_mut(&key)
3236                    .unwrap()
3237                    .blocks[block_index]
3238                    .max_block_score = max_block_score;
3239                max_list_score = f32::max(max_list_score, max_block_score);
3240            }
3241            index.segments_index[key0]
3242                .segment
3243                .get_mut(&key)
3244                .unwrap()
3245                .max_list_score = max_list_score;
3246        }
3247    }
3248}
3249
3250/// Loads the index from disk into RAM or MMAP.
3251/// * `index_path` - index path.  
3252/// * `mute` - prevent emitting status messages (e.g. when using pipes for data interprocess communication).  
3253pub(crate) async fn open_shard(
3254    index_path: &Path,
3255    mute: bool,
3256    vector_type: Precision,
3257    vector_dimensions: usize,
3258) -> Result<ShardArc, String> {
3259    if !mute {
3260        println!("opening index ...");
3261    }
3262
3263    let mut index_mmap_position = INDEX_HEADER_SIZE as usize;
3264    let mut docstore_mmap_position = 0;
3265
3266    let vector_size = size_of::<VectorHeader>()
3267        + (vector_dimensions
3268            * match vector_type {
3269                Precision::F32 => 4,
3270                Precision::I8 => 1,
3271                Precision::None => 0,
3272            });
3273
3274    match File::open(Path::new(index_path).join(META_FILENAME)) {
3275        Ok(meta_file) => {
3276            let meta: IndexMetaObject = serde_json::from_reader(BufReader::new(meta_file)).unwrap();
3277
3278            match File::open(Path::new(index_path).join(SCHEMA_FILENAME)) {
3279                Ok(schema_file) => {
3280                    let schema = serde_json::from_reader(BufReader::new(schema_file)).unwrap();
3281
3282                    let synonyms = if let Ok(synonym_file) =
3283                        File::open(Path::new(index_path).join(SYNONYMS_FILENAME))
3284                    {
3285                        serde_json::from_reader(BufReader::new(synonym_file)).unwrap_or_default()
3286                    } else {
3287                        Vec::new()
3288                    };
3289
3290                    match create_shard(index_path, &meta, &schema, false, &synonyms, 11, mute, None)
3291                    {
3292                        Ok(mut shard) => {
3293                            let mut block_count_sum = 0;
3294
3295                            let is_mmap = shard.meta.access_type == AccessType::Mmap;
3296
3297                            let file_len = if is_mmap {
3298                                shard.index_file_mmap.len() as u64
3299                            } else {
3300                                shard.index_file.metadata().unwrap().len()
3301                            };
3302
3303                            while if is_mmap {
3304                                index_mmap_position as u64
3305                            } else {
3306                                shard.index_file.stream_position().unwrap()
3307                            } < file_len
3308                            {
3309                                let mut segment_head_vec: Vec<(u32, u32)> = Vec::new();
3310                                for key0 in 0..shard.segment_number1 {
3311                                    if key0 == 0 {
3312                                        shard.last_level_index_file_start_pos = if is_mmap {
3313                                            index_mmap_position as u64
3314                                        } else {
3315                                            shard.index_file.stream_position().unwrap()
3316                                        };
3317
3318                                        shard.last_level_docstore_file_start_pos = if is_mmap {
3319                                            docstore_mmap_position as u64
3320                                        } else {
3321                                            shard.docstore_file.stream_position().unwrap()
3322                                        };
3323
3324                                        if shard.level_index.is_empty() {
3325                                            let longest_field_id = if is_mmap {
3326                                                read_u16_ref(
3327                                                    &shard.index_file_mmap,
3328                                                    &mut index_mmap_position,
3329                                                )
3330                                                    as usize
3331                                            } else {
3332                                                let _ = shard.index_file.read(
3333                                                    &mut shard
3334                                                        .compressed_index_segment_block_buffer
3335                                                        [0..2],
3336                                                );
3337                                                read_u16(
3338                                                    &shard.compressed_index_segment_block_buffer,
3339                                                    0,
3340                                                )
3341                                                    as usize
3342                                            };
3343
3344                                            for indexed_field in shard.indexed_field_vec.iter_mut()
3345                                            {
3346                                                indexed_field.is_longest_field = indexed_field
3347                                                    .indexed_field_id
3348                                                    == longest_field_id;
3349
3350                                                if indexed_field.is_longest_field {
3351                                                    shard.longest_field_id = longest_field_id
3352                                                }
3353                                            }
3354                                        }
3355
3356                                        let mut document_length_compressed_array_vec: Vec<
3357                                            [u8; ROARING_BLOCK_SIZE],
3358                                        > = Vec::new();
3359
3360                                        let document_length_compressed_array_pointer = if is_mmap {
3361                                            index_mmap_position
3362                                        } else {
3363                                            shard.index_file.stream_position().unwrap() as usize
3364                                        };
3365
3366                                        for _i in 0..shard.indexed_field_vec.len() {
3367                                            if is_mmap {
3368                                                index_mmap_position += ROARING_BLOCK_SIZE;
3369                                            } else {
3370                                                let mut document_length_compressed_array_item =
3371                                                    [0u8; ROARING_BLOCK_SIZE];
3372
3373                                                let _ = shard.index_file.read(
3374                                                    &mut document_length_compressed_array_item,
3375                                                );
3376                                                document_length_compressed_array_vec
3377                                                    .push(document_length_compressed_array_item);
3378                                            }
3379                                        }
3380
3381                                        let mut docstore_pointer_docs: Vec<u8> = Vec::new();
3382
3383                                        let mut docstore_pointer_docs_pointer = 0;
3384                                        if !shard.stored_field_names.is_empty() {
3385                                            if is_mmap {
3386                                                let docstore_pointer_docs_size = read_u32_ref(
3387                                                    &shard.docstore_file_mmap,
3388                                                    &mut docstore_mmap_position,
3389                                                )
3390                                                    as usize;
3391                                                docstore_pointer_docs_pointer =
3392                                                    docstore_mmap_position;
3393                                                docstore_mmap_position +=
3394                                                    docstore_pointer_docs_size;
3395                                            } else {
3396                                                let _ = shard.docstore_file.read(
3397                                                    &mut shard
3398                                                        .compressed_index_segment_block_buffer
3399                                                        [0..4],
3400                                                );
3401
3402                                                let docstore_pointer_docs_size = read_u32(
3403                                                    &shard.compressed_index_segment_block_buffer,
3404                                                    0,
3405                                                )
3406                                                    as usize;
3407
3408                                                docstore_pointer_docs_pointer =
3409                                                    shard.docstore_file.stream_position().unwrap()
3410                                                        as usize;
3411                                                docstore_pointer_docs =
3412                                                    vec![0; docstore_pointer_docs_size];
3413                                                let _ = shard
3414                                                    .docstore_file
3415                                                    .read(&mut docstore_pointer_docs);
3416                                            }
3417                                        }
3418
3419                                        if is_mmap {
3420                                            let _previous_indexed_doc_count =
3421                                                shard.indexed_doc_count;
3422                                            shard.indexed_doc_count = read_u64_ref(
3423                                                &shard.index_file_mmap,
3424                                                &mut index_mmap_position,
3425                                            )
3426                                                as usize;
3427                                            shard.positions_sum_normalized = read_u64_ref(
3428                                                &shard.index_file_mmap,
3429                                                &mut index_mmap_position,
3430                                            );
3431
3432                                            for _key0 in 0..shard.segment_number1 {
3433                                                let block_length = read_u32_ref(
3434                                                    &shard.index_file_mmap,
3435                                                    &mut index_mmap_position,
3436                                                );
3437                                                let key_count = read_u32_ref(
3438                                                    &shard.index_file_mmap,
3439                                                    &mut index_mmap_position,
3440                                                );
3441
3442                                                segment_head_vec.push((block_length, key_count));
3443                                            }
3444                                        } else {
3445                                            let _ = shard.index_file.read(
3446                                                &mut shard.compressed_index_segment_block_buffer
3447                                                    [0..16],
3448                                            );
3449
3450                                            shard.indexed_doc_count = read_u64(
3451                                                &shard.compressed_index_segment_block_buffer,
3452                                                0,
3453                                            )
3454                                                as usize;
3455
3456                                            shard.positions_sum_normalized = read_u64(
3457                                                &shard.compressed_index_segment_block_buffer,
3458                                                8,
3459                                            );
3460
3461                                            for _key0 in 0..shard.segment_number1 {
3462                                                let _ = shard.index_file.read(
3463                                                    &mut shard
3464                                                        .compressed_index_segment_block_buffer
3465                                                        [0..8],
3466                                                );
3467
3468                                                let block_length = read_u32(
3469                                                    &shard.compressed_index_segment_block_buffer,
3470                                                    0,
3471                                                );
3472                                                let key_count = read_u32(
3473                                                    &shard.compressed_index_segment_block_buffer,
3474                                                    4,
3475                                                );
3476                                                segment_head_vec.push((block_length, key_count));
3477                                            }
3478                                        }
3479
3480                                        shard.document_length_normalized_average =
3481                                            shard.positions_sum_normalized as f32
3482                                                / shard.indexed_doc_count as f32;
3483
3484                                        shard.level_index.push(LevelIndex {
3485                                            document_length_compressed_array:
3486                                                document_length_compressed_array_vec,
3487                                            docstore_pointer_docs,
3488                                            docstore_pointer_docs_pointer,
3489                                            document_length_compressed_array_pointer,
3490                                        });
3491                                    }
3492
3493                                    let block_length = segment_head_vec[key0].0;
3494                                    let key_count = segment_head_vec[key0].1;
3495
3496                                    let block_id =
3497                                        (block_count_sum >> shard.segment_number_bits1) as u32;
3498                                    block_count_sum += 1;
3499
3500                                    let key_body_pointer_write_start: u32 =
3501                                        key_count * shard.key_head_size as u32;
3502
3503                                    if is_mmap {
3504                                        index_mmap_position +=
3505                                            key_count as usize * shard.key_head_size;
3506                                        shard.segments_index[key0].byte_array_blocks_pointer.push(
3507                                            (
3508                                                index_mmap_position,
3509                                                (block_length - key_body_pointer_write_start)
3510                                                    as usize,
3511                                                key_count,
3512                                            ),
3513                                        );
3514
3515                                        index_mmap_position +=
3516                                            (block_length - key_body_pointer_write_start) as usize;
3517                                    } else {
3518                                        let _ = shard.index_file.read(
3519                                            &mut shard.compressed_index_segment_block_buffer
3520                                                [0..(key_count as usize * shard.key_head_size)],
3521                                        );
3522                                        let compressed_index_segment_block_buffer = &shard
3523                                            .compressed_index_segment_block_buffer
3524                                            [0..(key_count as usize * shard.key_head_size)];
3525
3526                                        let mut block_array: Vec<u8> = vec![
3527                                            0;
3528                                            (block_length - key_body_pointer_write_start)
3529                                                as usize
3530                                        ];
3531
3532                                        let _ = shard.index_file.read(&mut block_array);
3533                                        shard.segments_index[key0]
3534                                            .byte_array_blocks
3535                                            .push(block_array);
3536
3537                                        let mut read_pointer = 0;
3538
3539                                        let mut posting_count_previous = 0;
3540                                        let mut pointer_pivot_p_docid_previous = 0;
3541                                        let mut compression_type_pointer_previous = 0;
3542
3543                                        for key_index in 0..key_count {
3544                                            let key_hash = read_u64_ref(
3545                                                compressed_index_segment_block_buffer,
3546                                                &mut read_pointer,
3547                                            );
3548
3549                                            let posting_count = read_u16_ref(
3550                                                compressed_index_segment_block_buffer,
3551                                                &mut read_pointer,
3552                                            );
3553
3554                                            let max_docid = read_u16_ref(
3555                                                compressed_index_segment_block_buffer,
3556                                                &mut read_pointer,
3557                                            );
3558
3559                                            let max_p_docid = read_u16_ref(
3560                                                compressed_index_segment_block_buffer,
3561                                                &mut read_pointer,
3562                                            );
3563
3564                                            let mut posting_count_ngram_1 = 0;
3565                                            let mut posting_count_ngram_2 = 0;
3566                                            let mut posting_count_ngram_3 = 0;
3567                                            match shard.key_head_size {
3568                                                20 => {}
3569                                                22 => {
3570                                                    let posting_count_ngram_1_compressed =
3571                                                        read_u8_ref(
3572                                                            compressed_index_segment_block_buffer,
3573                                                            &mut read_pointer,
3574                                                        );
3575                                                    posting_count_ngram_1 =
3576                                                        DOCUMENT_LENGTH_COMPRESSION
3577                                                            [posting_count_ngram_1_compressed
3578                                                                as usize];
3579
3580                                                    let posting_count_ngram_2_compressed =
3581                                                        read_u8_ref(
3582                                                            compressed_index_segment_block_buffer,
3583                                                            &mut read_pointer,
3584                                                        );
3585                                                    posting_count_ngram_2 =
3586                                                        DOCUMENT_LENGTH_COMPRESSION
3587                                                            [posting_count_ngram_2_compressed
3588                                                                as usize];
3589                                                }
3590                                                _ => {
3591                                                    let posting_count_ngram_1_compressed =
3592                                                        read_u8_ref(
3593                                                            compressed_index_segment_block_buffer,
3594                                                            &mut read_pointer,
3595                                                        );
3596                                                    posting_count_ngram_1 =
3597                                                        DOCUMENT_LENGTH_COMPRESSION
3598                                                            [posting_count_ngram_1_compressed
3599                                                                as usize];
3600
3601                                                    let posting_count_ngram_2_compressed =
3602                                                        read_u8_ref(
3603                                                            compressed_index_segment_block_buffer,
3604                                                            &mut read_pointer,
3605                                                        );
3606                                                    posting_count_ngram_2 =
3607                                                        DOCUMENT_LENGTH_COMPRESSION
3608                                                            [posting_count_ngram_2_compressed
3609                                                                as usize];
3610
3611                                                    let posting_count_ngram_3_compressed =
3612                                                        read_u8_ref(
3613                                                            compressed_index_segment_block_buffer,
3614                                                            &mut read_pointer,
3615                                                        );
3616                                                    posting_count_ngram_3 =
3617                                                        DOCUMENT_LENGTH_COMPRESSION
3618                                                            [posting_count_ngram_3_compressed
3619                                                                as usize];
3620                                                }
3621                                            }
3622
3623                                            let pointer_pivot_p_docid = read_u16_ref(
3624                                                compressed_index_segment_block_buffer,
3625                                                &mut read_pointer,
3626                                            );
3627
3628                                            let compression_type_pointer = read_u32_ref(
3629                                                compressed_index_segment_block_buffer,
3630                                                &mut read_pointer,
3631                                            );
3632
3633                                            if let Some(value) = shard.segments_index[key0]
3634                                                .segment
3635                                                .get_mut(&key_hash)
3636                                            {
3637                                                value.posting_count += posting_count as u32 + 1;
3638
3639                                                value.blocks.push(BlockObjectIndex {
3640                                                    max_block_score: 0.0,
3641                                                    block_id,
3642                                                    posting_count,
3643                                                    max_docid,
3644                                                    max_p_docid,
3645                                                    pointer_pivot_p_docid,
3646                                                    compression_type_pointer,
3647                                                });
3648                                            } else {
3649                                                let value = PostingListObjectIndex {
3650                                                    posting_count: posting_count as u32 + 1,
3651                                                    posting_count_ngram_1,
3652                                                    posting_count_ngram_2,
3653                                                    posting_count_ngram_3,
3654                                                    max_list_score: 0.0,
3655                                                    position_range_previous: 0,
3656                                                    blocks: vec![BlockObjectIndex {
3657                                                        max_block_score: 0.0,
3658                                                        block_id,
3659                                                        posting_count,
3660                                                        max_docid,
3661                                                        max_p_docid,
3662                                                        pointer_pivot_p_docid,
3663                                                        compression_type_pointer,
3664                                                    }],
3665                                                    ..Default::default()
3666                                                };
3667                                                shard.segments_index[key0]
3668                                                    .segment
3669                                                    .insert(key_hash, value);
3670                                            };
3671
3672                                            if !shard
3673                                                .indexed_doc_count
3674                                                .is_multiple_of(ROARING_BLOCK_SIZE)
3675                                                && block_id as usize
3676                                                    == shard.indexed_doc_count / ROARING_BLOCK_SIZE
3677                                                && shard.meta.access_type == AccessType::Ram
3678                                            {
3679                                                let position_range_previous = if key_index == 0 {
3680                                                    0
3681                                                } else {
3682                                                    let posting_pointer_size_sum_previous =
3683                                                        pointer_pivot_p_docid_previous as usize * 2
3684                                                            + if (pointer_pivot_p_docid_previous
3685                                                                as usize)
3686                                                                < posting_count_previous
3687                                                            {
3688                                                                (posting_count_previous
3689                                                                    - pointer_pivot_p_docid_previous
3690                                                                        as usize)
3691                                                                    * 3
3692                                                            } else {
3693                                                                0
3694                                                            };
3695
3696                                                    let rank_position_pointer_range_previous= compression_type_pointer_previous & 0b0011_1111_1111_1111_1111_1111_1111_1111;
3697                                                    let compression_type_previous: CompressionType =
3698                                                        FromPrimitive::from_u32(
3699                                                            compression_type_pointer_previous >> 30,
3700                                                        )
3701                                                        .unwrap();
3702
3703                                                    let compressed_docid_previous =
3704                                                        match compression_type_previous {
3705                                                            CompressionType::Array => {
3706                                                                posting_count_previous * 2
3707                                                            }
3708                                                            CompressionType::Bitmap => 8192,
3709                                                            CompressionType::Rle => {
3710                                                                let byte_array_docid = &shard
3711                                                                    .segments_index[key0]
3712                                                                    .byte_array_blocks
3713                                                                    [block_id as usize];
3714                                                                4 * read_u16( byte_array_docid, rank_position_pointer_range_previous as usize +posting_pointer_size_sum_previous) as usize + 2
3715                                                            }
3716                                                            _ => 0,
3717                                                        };
3718
3719                                                    rank_position_pointer_range_previous
3720                                                        + (posting_pointer_size_sum_previous
3721                                                            + compressed_docid_previous)
3722                                                            as u32
3723                                                };
3724
3725                                                let plo = shard.segments_index[key0]
3726                                                    .segment
3727                                                    .get_mut(&key_hash)
3728                                                    .unwrap();
3729
3730                                                plo.position_range_previous =
3731                                                    position_range_previous;
3732
3733                                                posting_count_previous = posting_count as usize + 1;
3734                                                pointer_pivot_p_docid_previous =
3735                                                    pointer_pivot_p_docid;
3736                                                compression_type_pointer_previous =
3737                                                    compression_type_pointer;
3738                                            };
3739                                        }
3740                                    }
3741                                }
3742                            }
3743
3744                            shard.committed_doc_count = shard.indexed_doc_count;
3745                            shard.is_last_level_incomplete =
3746                                !shard.committed_doc_count.is_multiple_of(ROARING_BLOCK_SIZE);
3747
3748                            if shard.is_vector_indexing && !shard.vector_file_mmap.is_empty() {
3749                                shard.indexed_vector_count = 0;
3750
3751                                let mut offset = 0;
3752                                for _level_id in 0..shard.level_index.len() {
3753                                    shard.last_level_vector_file_start_pos = offset as u64;
3754
3755                                    let cluster_number_bytes =
3756                                        &shard.vector_file_mmap[offset..offset + 4];
3757                                    let cluster_number = u32::from_le_bytes(
3758                                        cluster_number_bytes.try_into().unwrap(),
3759                                    )
3760                                        as usize;
3761                                    offset += 4;
3762
3763                                    let mut level_vectors_count = 0;
3764                                    let mut start_index = 0;
3765                                    for _i in 0..cluster_number {
3766                                        let cluster_header_bytes =
3767                                            &shard.vector_file_mmap[offset..offset + 4];
3768                                        let cluster_header = ClusterHeader {
3769                                            start_index,
3770                                            child_count: u32::from_le_bytes(
3771                                                cluster_header_bytes.try_into().unwrap(),
3772                                            ),
3773                                        };
3774                                        offset += 4;
3775                                        start_index += cluster_header.child_count;
3776                                        level_vectors_count += cluster_header.child_count;
3777                                    }
3778
3779                                    shard.indexed_vector_count += level_vectors_count as usize;
3780                                    shard.indexed_cluster_count += cluster_number;
3781
3782                                    offset += level_vectors_count as usize * vector_size;
3783                                }
3784                            }
3785
3786                            for (i, component) in shard.bm25_component_cache.iter_mut().enumerate()
3787                            {
3788                                let document_length_quotient = DOCUMENT_LENGTH_COMPRESSION[i]
3789                                    as f32
3790                                    / shard.document_length_normalized_average;
3791                                *component = K * (1.0 - B + B * document_length_quotient);
3792                            }
3793
3794                            shard.string_set_to_single_term_id();
3795
3796                            update_list_max_impact_score(&mut shard);
3797
3798                            let mut reader = BufReader::with_capacity(8192, &shard.delete_file);
3799                            while let Ok(buffer) = reader.fill_buf() {
3800                                let length = buffer.len();
3801
3802                                if length == 0 {
3803                                    break;
3804                                }
3805
3806                                for i in (0..length).step_by(8) {
3807                                    let docid = read_u64(buffer, i);
3808                                    shard.delete_hashset.insert(docid as usize);
3809                                }
3810
3811                                reader.consume(length);
3812                            }
3813
3814                            let shard_arc = Arc::new(RwLock::new(shard));
3815
3816                            warmup(&shard_arc).await;
3817                            Ok(shard_arc.clone())
3818                        }
3819                        Err(err) => Err(err.to_string()),
3820                    }
3821                }
3822                Err(err) => Err(err.to_string()),
3823            }
3824        }
3825        Err(err) => Err(err.to_string()),
3826    }
3827}
3828
3829/// Loads the index from disk into RAM or MMAP.
3830/// * `index_path` - index path.  
3831/// * `mute` - prevent emitting status messages (e.g. when using pipes for data interprocess communication).  
3832pub async fn open_index(index_path: &Path) -> Result<IndexArc, String> {
3833    let start_time = Instant::now();
3834
3835    match File::open(Path::new(index_path).join(META_FILENAME)) {
3836        Ok(meta_file) => {
3837            let meta: IndexMetaObject = serde_json::from_reader(BufReader::new(meta_file)).unwrap();
3838
3839            match File::open(Path::new(index_path).join(SCHEMA_FILENAME)) {
3840                Ok(schema_file) => {
3841                    let schema = serde_json::from_reader(BufReader::new(schema_file)).unwrap();
3842
3843                    let synonyms = if let Ok(synonym_file) =
3844                        File::open(Path::new(index_path).join(SYNONYMS_FILENAME))
3845                    {
3846                        serde_json::from_reader(BufReader::new(synonym_file)).unwrap_or_default()
3847                    } else {
3848                        Vec::new()
3849                    };
3850
3851                    let shard_number = index_path
3852                        .join("shards")
3853                        .read_dir()
3854                        .unwrap()
3855                        .filter_map(Result::ok)
3856                        .filter(|entry| entry.path().is_dir())
3857                        .filter_map(|entry| entry.file_name().into_string().ok())
3858                        .filter(|name| name.parse::<usize>().is_ok())
3859                        .count();
3860
3861                    match create_index_root(
3862                        index_path,
3863                        meta,
3864                        &schema,
3865                        false,
3866                        &synonyms,
3867                        11,
3868                        false,
3869                        Some(shard_number),
3870                    )
3871                    .await
3872                    {
3873                        Ok(index_arc) => {
3874                            let lock = Arc::into_inner(index_arc).unwrap();
3875                            let index = RwLock::into_inner(lock);
3876
3877                            let index_arc = Arc::new(RwLock::new(index));
3878
3879                            if let Some(symspell) =
3880                                &mut index_arc.read().await.symspell_option.as_ref()
3881                            {
3882                                let dictionary_path =
3883                                    Path::new(&index_arc.read().await.index_path_string)
3884                                        .join(DICTIONARY_FILENAME);
3885                                let _ = symspell.write().await.load_dictionary(
3886                                    &dictionary_path,
3887                                    0,
3888                                    1,
3889                                    " ",
3890                                );
3891                            }
3892
3893                            if let Some(completion_option) =
3894                                &mut index_arc.read().await.completion_option.as_ref()
3895                            {
3896                                let _ = completion_option.write().await.load_completions(
3897                                    &Path::new(&index_arc.read().await.index_path_string)
3898                                        .join(COMPLETIONS_FILENAME),
3899                                    0,
3900                                    1,
3901                                    ":",
3902                                );
3903                            }
3904
3905                            let mut shard_vec: Vec<Arc<RwLock<Shard>>> = Vec::new();
3906
3907                            let vector_type = match index_arc.read().await.quantization {
3908                                Quantization::ScalarQuantizationI8 => Precision::I8,
3909                                Quantization::TurboQuantI8 => Precision::I8,
3910                                _ => index_arc.read().await.vector_precision,
3911                            };
3912
3913                            let dimensions = index_arc.read().await.vector_dimensions;
3914
3915                            let paths: Vec<_> = fs::read_dir(index_path.join("shards"))
3916                                .unwrap()
3917                                .filter_map(Result::ok)
3918                                .collect();
3919                            let mut shard_handle_vec = Vec::new();
3920                            let index_path_clone = Arc::new(index_path.to_path_buf());
3921                            for i in 0..paths.len() {
3922                                let index_path_clone2 = index_path_clone.clone();
3923                                let vector_type_clone = vector_type;
3924                                let dimensions_clone = dimensions;
3925                                shard_handle_vec.push(tokio::spawn(async move {
3926                                    let path = index_path_clone2.join("shards").join(i.to_string());
3927                                    open_shard(&path, true, vector_type_clone, dimensions_clone)
3928                                        .await
3929                                        .unwrap()
3930                                }));
3931                            }
3932
3933                            for shard_handle in shard_handle_vec {
3934                                let shard_arc = shard_handle.await.unwrap();
3935                                shard_arc.write().await.index_option = Some(index_arc.clone());
3936
3937                                shard_arc.write().await.quantization =
3938                                    index_arc.read().await.quantization;
3939                                shard_arc.write().await.shard_number =
3940                                    index_arc.read().await.shard_number;
3941                                shard_arc.write().await.vector_dimensions =
3942                                    index_arc.read().await.vector_dimensions;
3943                                shard_arc.write().await.vector_dimensions_original =
3944                                    index_arc.read().await.vector_dimensions_original;
3945                                shard_arc.write().await.vector_precision =
3946                                    index_arc.read().await.vector_precision;
3947                                shard_arc.write().await.vector_similarity =
3948                                    index_arc.read().await.vector_similarity;
3949                                shard_arc.write().await.is_avx2 = index_arc.read().await.is_avx2;
3950                                shard_arc.write().await.is_neon = index_arc.read().await.is_neon;
3951                                shard_arc.write().await.is_simd = index_arc.read().await.is_simd;
3952                                shard_arc.write().await.chunk_size =
3953                                    index_arc.read().await.chunk_size;
3954
3955                                shard_arc.write().await.turbo_quant =
3956                                    index_arc.read().await.turbo_quant.clone();
3957
3958                                if shard_arc.read().await.is_vector_indexing
3959                                    && !shard_arc.read().await.vector_file_mmap.is_empty()
3960                                    && shard_arc.read().await.quantization
3961                                        == Quantization::ScalarQuantizationI8
3962                                    && shard_arc.read().await.vector_similarity
3963                                        == VectorSimilarity::Euclidean
3964                                {
3965                                    let (min_vector_value, max_vector_value) = read_min_max(
3966                                        &shard_arc.read().await.vector_file_mmap,
3967                                        shard_arc.read().await.vector_dimensions,
3968                                    );
3969                                    shard_arc.write().await.min_vector_value = min_vector_value;
3970                                    shard_arc.write().await.max_vector_value = max_vector_value;
3971                                }
3972
3973                                index_arc.write().await.indexed_doc_count +=
3974                                    shard_arc.read().await.indexed_doc_count;
3975                                index_arc.write().await.indexed_vector_count +=
3976                                    shard_arc.read().await.indexed_vector_count;
3977                                index_arc.write().await.indexed_cluster_count +=
3978                                    shard_arc.read().await.indexed_cluster_count;
3979                                index_arc.write().await.deleted_doc_count +=
3980                                    shard_arc.read().await.delete_hashset.len();
3981                                let _shard_id = shard_arc.read().await.meta.id;
3982                                shard_vec.push(shard_arc);
3983                            }
3984
3985                            let indexed_doc_count = index_arc.read().await.indexed_doc_count;
3986                            *index_arc.write().await.docid_global.write().await = indexed_doc_count;
3987
3988                            index_arc.write().await.shard_number = shard_vec.len();
3989
3990                            index_arc.write().await.shard_vec = shard_vec;
3991
3992                            let _elapsed_time = start_time.elapsed().as_nanos();
3993
3994                            Ok(index_arc.clone())
3995                        }
3996                        Err(err) => Err(err.to_string()),
3997                    }
3998                }
3999                Err(err) => Err(err.to_string()),
4000            }
4001        }
4002        Err(err) => Err(err.to_string()),
4003    }
4004}
4005
4006pub(crate) async fn warmup(shard_object_arc: &ShardArc) {
4007    shard_object_arc.write().await.frequentword_results.clear();
4008    let mut query_facets: Vec<QueryFacet> = Vec::new();
4009    for facet in shard_object_arc.read().await.facets.iter() {
4010        match facet.field_type {
4011            FieldType::String16 => query_facets.push(QueryFacet::String16 {
4012                field: facet.name.clone(),
4013                prefix: "".into(),
4014                length: u16::MAX,
4015            }),
4016            FieldType::String32 => query_facets.push(QueryFacet::String32 {
4017                field: facet.name.clone(),
4018                prefix: "".into(),
4019                length: u32::MAX,
4020            }),
4021            FieldType::StringSet16 => query_facets.push(QueryFacet::StringSet16 {
4022                field: facet.name.clone(),
4023                prefix: "".into(),
4024                length: u16::MAX,
4025            }),
4026            FieldType::StringSet32 => query_facets.push(QueryFacet::StringSet32 {
4027                field: facet.name.clone(),
4028                prefix: "".into(),
4029                length: u32::MAX,
4030            }),
4031            _ => {}
4032        }
4033    }
4034
4035    let frequent_words = shard_object_arc.read().await.frequent_words.clone();
4036    for frequentword in frequent_words.iter() {
4037        let results_list = shard_object_arc
4038            .search_lexical_shard(
4039                frequentword.to_owned(),
4040                QueryType::Union,
4041                false,
4042                0,
4043                1000,
4044                ResultType::TopkCount,
4045                false,
4046                Vec::new(),
4047                query_facets.clone(),
4048                Vec::new(),
4049                Vec::new(),
4050            )
4051            .await;
4052
4053        let mut index_mut = shard_object_arc.write().await;
4054        index_mut
4055            .frequentword_results
4056            .insert(frequentword.to_string(), results_list);
4057    }
4058}
4059
4060#[derive(Default, Debug, Deserialize, Serialize, Clone)]
4061pub(crate) struct TermObject {
4062    pub key_hash: u64,
4063    pub key0: u32,
4064    pub term: String,
4065
4066    pub ngram_type: NgramType,
4067
4068    pub term_ngram_2: String,
4069    pub term_ngram_1: String,
4070    pub term_ngram_0: String,
4071    pub field_vec_ngram1: Vec<(usize, u32)>,
4072    pub field_vec_ngram2: Vec<(usize, u32)>,
4073    pub field_vec_ngram3: Vec<(usize, u32)>,
4074
4075    pub field_positions_vec: Vec<Vec<u16>>,
4076}
4077
4078#[derive(Default, Debug, Serialize, Deserialize, Clone)]
4079pub(crate) struct NonUniqueTermObject {
4080    pub term: String,
4081    pub ngram_type: NgramType,
4082
4083    pub term_ngram_2: String,
4084    pub term_ngram_1: String,
4085    pub term_ngram_0: String,
4086    pub op: QueryType,
4087}
4088
4089/// system endianess: true: the system is little endian, false: the system is big endian (network byte order).
4090pub static IS_SYSTEM_LE: LazyLock<bool> = LazyLock::new(|| u16::from_ne_bytes([1, 0]) == 1);
4091
4092/// AVX2 support enabled
4093pub static IS_AVX2: LazyLock<bool> = LazyLock::new(|| {
4094    #[cfg(target_arch = "x86_64")]
4095    let is_avx2 = is_x86_feature_detected!("avx2");
4096    #[cfg(not(target_arch = "x86_64"))]
4097    let is_avx2 = false;
4098    is_avx2
4099});
4100
4101/// NEON support enabled (always `true` on aarch64 — Advanced SIMD is a
4102/// mandatory part of ARMv8). The flag exists for parity with `IS_AVX2`
4103/// so the rest of the crate can branch on it the same way.
4104pub static IS_NEON: LazyLock<bool> = LazyLock::new(|| {
4105    #[cfg(target_arch = "aarch64")]
4106    let is_neon = std::arch::is_aarch64_feature_detected!("neon");
4107    #[cfg(not(target_arch = "aarch64"))]
4108    let is_neon = false;
4109    is_neon
4110});
4111
4112/// Any SIMD backend (AVX2 / NEON) is available. Used by call sites to gate
4113/// the SIMD path; on archs without a backend this is always `false`.
4114pub static IS_SIMD: LazyLock<bool> = LazyLock::new(|| *IS_AVX2 || *IS_NEON);
4115
4116#[cfg(not(any(
4117    all(
4118        feature = "gxhash",
4119        target_arch = "x86_64",
4120        target_feature = "aes",
4121        target_feature = "sse2"
4122    ),
4123    all(
4124        feature = "gxhash",
4125        target_arch = "aarch64",
4126        target_feature = "aes",
4127        target_feature = "neon"
4128    )
4129)))]
4130pub(crate) static HASHER_32: LazyLock<RandomState> =
4131    LazyLock::new(|| RandomState::with_seeds(805272099, 242851902, 646123436, 591410655));
4132
4133#[cfg(not(any(
4134    all(
4135        feature = "gxhash",
4136        target_arch = "x86_64",
4137        target_feature = "aes",
4138        target_feature = "sse2"
4139    ),
4140    all(
4141        feature = "gxhash",
4142        target_arch = "aarch64",
4143        target_feature = "aes",
4144        target_feature = "neon"
4145    )
4146)))]
4147pub(crate) static HASHER_64: LazyLock<RandomState> =
4148    LazyLock::new(|| RandomState::with_seeds(808259318, 750368348, 84901999, 789810389));
4149
4150#[inline]
4151#[cfg(any(
4152    all(
4153        feature = "gxhash",
4154        target_arch = "x86_64",
4155        target_feature = "aes",
4156        target_feature = "sse2"
4157    ),
4158    all(
4159        feature = "gxhash",
4160        target_arch = "aarch64",
4161        target_feature = "aes",
4162        target_feature = "neon"
4163    )
4164))]
4165pub(crate) fn hash32(term_bytes: &[u8]) -> u32 {
4166    gxhash32(term_bytes, 1234)
4167}
4168
4169#[inline]
4170#[cfg(any(
4171    all(
4172        feature = "gxhash",
4173        target_arch = "x86_64",
4174        target_feature = "aes",
4175        target_feature = "sse2"
4176    ),
4177    all(
4178        feature = "gxhash",
4179        target_arch = "aarch64",
4180        target_feature = "aes",
4181        target_feature = "neon"
4182    )
4183))]
4184pub(crate) fn hash64(term_bytes: &[u8]) -> u64 {
4185    gxhash64(term_bytes, 1234) & 0b1111111111111111111111111111111111111111111111111111111111111000
4186}
4187
4188#[inline]
4189#[cfg(not(any(
4190    all(
4191        feature = "gxhash",
4192        target_arch = "x86_64",
4193        target_feature = "aes",
4194        target_feature = "sse2"
4195    ),
4196    all(
4197        feature = "gxhash",
4198        target_arch = "aarch64",
4199        target_feature = "aes",
4200        target_feature = "neon"
4201    )
4202)))]
4203pub(crate) fn hash32(term_bytes: &[u8]) -> u32 {
4204    HASHER_32.hash_one(term_bytes) as u32
4205}
4206
4207#[inline]
4208#[cfg(not(any(
4209    all(
4210        feature = "gxhash",
4211        target_arch = "x86_64",
4212        target_feature = "aes",
4213        target_feature = "sse2"
4214    ),
4215    all(
4216        feature = "gxhash",
4217        target_arch = "aarch64",
4218        target_feature = "aes",
4219        target_feature = "neon"
4220    )
4221)))]
4222pub(crate) fn hash64(term_bytes: &[u8]) -> u64 {
4223    HASHER_64.hash_one(term_bytes)
4224        & 0b1111111111111111111111111111111111111111111111111111111111111000
4225}
4226
4227static FREQUENT_EN: &str = include_str!("../assets/dictionaries/frequent_en.txt");
4228static FREQUENT_DE: &str = include_str!("../assets/dictionaries/frequent_de.txt");
4229static FREQUENT_FR: &str = include_str!("../assets/dictionaries/frequent_fr.txt");
4230static FREQUENT_ES: &str = include_str!("../assets/dictionaries/frequent_es.txt");
4231
4232pub(crate) const NUM_FREE_VALUES: u32 = 24;
4233
4234/// Compress an u32 to a byte, preserving 4 significant bits.
4235/// used for compressing n-gram frequent_term positions_count and doc/field length
4236/// Ported from Lucene SmallFloat.java https://github.com/apache/lucene/blob/main/lucene/core/src/java/org/apache/lucene/util/SmallFloat.java
4237pub(crate) fn int_to_byte4(i: u32) -> u8 {
4238    if i < NUM_FREE_VALUES {
4239        i as u8
4240    } else {
4241        let ii = i - NUM_FREE_VALUES;
4242        let num_bits = 32 - ii.leading_zeros();
4243        if num_bits < 4 {
4244            (NUM_FREE_VALUES + ii) as u8
4245        } else {
4246            let shift = num_bits - 4;
4247            (NUM_FREE_VALUES + (((ii >> shift) & 0x07) | (shift + 1) << 3)) as u8
4248        }
4249    }
4250}
4251
4252/// Decompress a byte that has been compressed with intToByte4(int), to an u32
4253/// used for pre-calculating DOCUMENT_LENGTH_COMPRESSION table. Decompressing n-gram frequent_term positions_count and doc/field length via table lookup.
4254/// Ported from Lucene SmallFloat.java https://github.com/apache/lucene/blob/main/lucene/core/src/java/org/apache/lucene/util/SmallFloat.java
4255pub(crate) const fn byte4_to_int(b: u8) -> u32 {
4256    if (b as u32) < NUM_FREE_VALUES {
4257        b as u32
4258    } else {
4259        let i = b as u32 - NUM_FREE_VALUES;
4260        let bits = i & 0x07;
4261        let shift = i >> 3;
4262        if shift == 0 {
4263            NUM_FREE_VALUES + bits
4264        } else {
4265            NUM_FREE_VALUES + ((bits | 0x08) << (shift - 1))
4266        }
4267    }
4268}
4269
4270/// Pre-calculated DOCUMENT_LENGTH_COMPRESSION table for fast lookup.
4271pub(crate) const DOCUMENT_LENGTH_COMPRESSION: [u32; 256] = {
4272    let mut k2 = [0; 256];
4273    let mut i = 0usize;
4274    while i < 256 {
4275        k2[i] = byte4_to_int(i as u8);
4276        i += 1;
4277    }
4278    k2
4279};
4280
4281impl Shard {
4282    pub(crate) fn string_set_to_single_term_id(&mut self) {
4283        for (i, facet) in self.facets.iter().enumerate() {
4284            if facet.field_type == FieldType::StringSet16
4285                || facet.field_type == FieldType::StringSet32
4286            {
4287                for (idx, value) in facet.values.iter().enumerate() {
4288                    for term in value.1.0.iter() {
4289                        self.string_set_to_single_term_id_vec[i]
4290                            .entry(term.to_string())
4291                            .or_insert(AHashSet::from_iter(vec![idx as u32]))
4292                            .insert(idx as u32);
4293                    }
4294                }
4295            }
4296        }
4297    }
4298
4299    /// Reset shard to empty, while maintaining schema
4300    async fn clear_shard(&mut self) {
4301        let semaphore = self.semaphore.clone();
4302        let permit = semaphore.acquire_owned().await.unwrap();
4303
4304        self.level_terms.clear();
4305
4306        let mut mmap_options = MmapOptions::new();
4307        let mmap: MmapMut = mmap_options.len(4).map_anon().unwrap();
4308        self.index_file_mmap = mmap
4309            .make_read_only()
4310            .expect("Unable to make Mmap read-only");
4311
4312        let _ = self.index_file.rewind();
4313        if let Err(e) = self.index_file.set_len(0) {
4314            println!(
4315                "Unable to index_file.set_len in clear_index {} {} {:?}",
4316                self.index_path_string, self.indexed_doc_count, e
4317            )
4318        };
4319
4320        if !self.compressed_docstore_segment_block_buffer.is_empty() {
4321            self.compressed_docstore_segment_block_buffer = vec![0; ROARING_BLOCK_SIZE * 4];
4322        };
4323
4324        write_u16(
4325            INDEX_FORMAT_VERSION_MAJOR,
4326            &mut self.compressed_index_segment_block_buffer,
4327            0,
4328        );
4329        write_u16(
4330            INDEX_FORMAT_VERSION_MINOR,
4331            &mut self.compressed_index_segment_block_buffer,
4332            2,
4333        );
4334
4335        let _ = self
4336            .index_file
4337            .write(&self.compressed_index_segment_block_buffer[0..INDEX_HEADER_SIZE as usize]);
4338        let _ = self.index_file.flush();
4339
4340        self.index_file_mmap =
4341            unsafe { Mmap::map(&self.index_file).expect("Unable to create Mmap") };
4342
4343        self.docstore_file_mmap = unsafe {
4344            MmapOptions::new()
4345                .len(0)
4346                .map(&self.docstore_file)
4347                .expect("Unable to create Mmap")
4348        };
4349
4350        let _ = self.docstore_file.rewind();
4351        if let Err(e) = self.docstore_file.set_len(0) {
4352            println!("Unable to docstore_file.set_len in clear_index {:?}", e)
4353        };
4354        let _ = self.docstore_file.flush();
4355
4356        let _ = self.delete_file.rewind();
4357        if let Err(e) = self.delete_file.set_len(0) {
4358            println!("Unable to delete_file.set_len in clear_index {:?}", e)
4359        };
4360        let _ = self.delete_file.flush();
4361        self.delete_hashset.clear();
4362
4363        self.facets_file_mmap = unsafe {
4364            MmapOptions::new()
4365                .len(0)
4366                .map_mut(&self.facets_file)
4367                .expect("Unable to create Mmap")
4368        };
4369        let _ = self.facets_file.rewind();
4370        if let Err(e) = self
4371            .facets_file
4372            .set_len((self.facets_size_sum * ROARING_BLOCK_SIZE) as u64)
4373        {
4374            println!("Unable to facets_file.set_len in clear_index {:?}", e)
4375        };
4376        let _ = self.facets_file.flush();
4377
4378        self.facets_file_mmap =
4379            unsafe { MmapMut::map_mut(&self.facets_file).expect("Unable to create Mmap") };
4380        let index_path = Path::new(&self.index_path_string);
4381        let _ = fs::remove_file(index_path.join(FACET_VALUES_FILENAME));
4382        for facet in self.facets.iter_mut() {
4383            facet.values.clear();
4384            facet.min = ValueType::None;
4385            facet.max = ValueType::None;
4386        }
4387
4388        if !self.stored_field_names.is_empty() && self.meta.access_type == AccessType::Mmap {
4389            self.docstore_file_mmap =
4390                unsafe { Mmap::map(&self.docstore_file).expect("Unable to create Mmap") };
4391        }
4392
4393        self.vector_file_mmap = unsafe {
4394            MmapOptions::new()
4395                .len(0)
4396                .map(&self.docstore_file)
4397                .expect("Unable to create Mmap")
4398        };
4399        let _ = self.vector_file.rewind();
4400        if let Err(e) = self.vector_file.set_len(0) {
4401            println!("Unable to vector_file.set_len in clear_index {:?}", e)
4402        };
4403        let _ = self.vector_file.flush();
4404        self.vector_file_mmap =
4405            unsafe { Mmap::map(&self.vector_file).expect("Unable to create Mmap") };
4406        self.indexed_vector_count = 0;
4407        self.indexed_cluster_count = 0;
4408
4409        self.document_length_normalized_average = 0.0;
4410        self.indexed_doc_count = 0;
4411        self.committed_doc_count = 0;
4412        self.positions_sum_normalized = 0;
4413
4414        self.level_index = Vec::new();
4415
4416        for segment in self.segments_index.iter_mut() {
4417            segment.byte_array_blocks.clear();
4418            segment.byte_array_blocks_pointer.clear();
4419            segment.segment.clear();
4420        }
4421
4422        for segment in self.segments_level0.iter_mut() {
4423            segment.segment.clear();
4424        }
4425
4426        self.key_count_sum = 0;
4427        self.block_id = 0;
4428        self.strip_compressed_sum = 0;
4429        self.postings_buffer_pointer = 0;
4430        self.docid_count = 0;
4431        self.size_compressed_docid_index = 0;
4432        self.size_compressed_positions_index = 0;
4433        self.position_count = 0;
4434        self.postinglist_count = 0;
4435
4436        self.is_last_level_incomplete = false;
4437
4438        drop(permit);
4439    }
4440
4441    pub(crate) fn get_index_string_facets_shard(
4442        &self,
4443        query_facets: Vec<QueryFacet>,
4444    ) -> Option<AHashMap<String, Facet>> {
4445        if self.facets.is_empty() {
4446            return None;
4447        }
4448
4449        let mut result_query_facets = Vec::new();
4450        if !query_facets.is_empty() {
4451            result_query_facets = vec![ResultFacet::default(); self.facets.len()];
4452            for query_facet in query_facets.iter() {
4453                match &query_facet {
4454                    QueryFacet::String16 {
4455                        field,
4456                        prefix,
4457                        length,
4458                    } => {
4459                        if let Some(idx) = self.facets_map.get(field)
4460                            && self.facets[*idx].field_type == FieldType::String16
4461                        {
4462                            result_query_facets[*idx] = ResultFacet {
4463                                field: field.clone(),
4464                                prefix: prefix.clone(),
4465                                length: *length as u32,
4466                                ..Default::default()
4467                            }
4468                        }
4469                    }
4470                    QueryFacet::StringSet16 {
4471                        field,
4472                        prefix,
4473                        length,
4474                    } => {
4475                        if let Some(idx) = self.facets_map.get(field)
4476                            && self.facets[*idx].field_type == FieldType::StringSet16
4477                        {
4478                            result_query_facets[*idx] = ResultFacet {
4479                                field: field.clone(),
4480                                prefix: prefix.clone(),
4481                                length: *length as u32,
4482                                ..Default::default()
4483                            }
4484                        }
4485                    }
4486
4487                    QueryFacet::String32 {
4488                        field,
4489                        prefix,
4490                        length,
4491                    } => {
4492                        if let Some(idx) = self.facets_map.get(field)
4493                            && self.facets[*idx].field_type == FieldType::String32
4494                        {
4495                            result_query_facets[*idx] = ResultFacet {
4496                                field: field.clone(),
4497                                prefix: prefix.clone(),
4498                                length: *length,
4499                                ..Default::default()
4500                            }
4501                        }
4502                    }
4503                    QueryFacet::StringSet32 {
4504                        field,
4505                        prefix,
4506                        length,
4507                    } => {
4508                        if let Some(idx) = self.facets_map.get(field)
4509                            && self.facets[*idx].field_type == FieldType::StringSet32
4510                        {
4511                            result_query_facets[*idx] = ResultFacet {
4512                                field: field.clone(),
4513                                prefix: prefix.clone(),
4514                                length: *length,
4515                                ..Default::default()
4516                            }
4517                        }
4518                    }
4519
4520                    _ => {}
4521                };
4522            }
4523        }
4524
4525        let mut facets: AHashMap<String, Facet> = AHashMap::new();
4526        for (i, facet) in result_query_facets.iter().enumerate() {
4527            if facet.length == 0 || self.facets[i].values.is_empty() {
4528                continue;
4529            }
4530
4531            if self.facets[i].field_type == FieldType::StringSet16
4532                || self.facets[i].field_type == FieldType::StringSet32
4533            {
4534                let mut hash_map: AHashMap<String, usize> = AHashMap::new();
4535                for value in self.facets[i].values.iter() {
4536                    for term in value.1.0.iter() {
4537                        *hash_map.entry(term.clone()).or_insert(0) += value.1.1;
4538                    }
4539                }
4540
4541                let v = hash_map
4542                    .iter()
4543                    .sorted_unstable_by(|a, b| b.1.cmp(a.1))
4544                    .map(|(a, c)| (a.to_string(), *c))
4545                    .filter(|(a, _c)| facet.prefix.is_empty() || a.starts_with(&facet.prefix))
4546                    .take(facet.length as usize)
4547                    .collect::<Vec<_>>();
4548
4549                if !v.is_empty() {
4550                    facets.insert(facet.field.clone(), v);
4551                }
4552            } else {
4553                let v = self.facets[i]
4554                    .values
4555                    .iter()
4556                    .sorted_unstable_by(|a, b| b.1.cmp(a.1))
4557                    .map(|(a, c)| (a.to_string(), c.1))
4558                    .filter(|(a, _c)| facet.prefix.is_empty() || a.starts_with(&facet.prefix))
4559                    .take(facet.length as usize)
4560                    .collect::<Vec<_>>();
4561
4562                if !v.is_empty() {
4563                    facets.insert(facet.field.clone(), v);
4564                }
4565            }
4566        }
4567
4568        Some(facets)
4569    }
4570}
4571
4572impl Index {
4573    /// Current document count: indexed document count - deleted document count
4574    pub async fn current_doc_count(&self) -> usize {
4575        let mut current_doc_count = 0;
4576        for shard in self.shard_vec.iter() {
4577            current_doc_count +=
4578                shard.read().await.indexed_doc_count - shard.read().await.delete_hashset.len();
4579        }
4580        current_doc_count
4581    }
4582
4583    /// are there uncommitted documents?
4584    pub async fn uncommitted_doc_count(&self) -> usize {
4585        let mut uncommitted_doc_count = 0;
4586        for shard in self.shard_vec.iter() {
4587            uncommitted_doc_count +=
4588                shard.read().await.indexed_doc_count - shard.read().await.committed_doc_count;
4589        }
4590        uncommitted_doc_count
4591    }
4592
4593    /// Get number of indexed documents.
4594    pub async fn committed_doc_count(&self) -> usize {
4595        let mut committed_doc_count = 0;
4596        for shard in self.shard_vec.iter() {
4597            committed_doc_count += shard.read().await.committed_doc_count;
4598        }
4599        committed_doc_count
4600    }
4601
4602    /// Get number of indexed documents.
4603    pub async fn indexed_doc_count(&self) -> usize {
4604        let mut indexed_doc_count = 0;
4605        for shard in self.shard_vec.iter() {
4606            indexed_doc_count += shard.read().await.indexed_doc_count;
4607        }
4608        indexed_doc_count
4609    }
4610
4611    /// Get number of indexed vectors.
4612    pub async fn indexed_vector_count(&self) -> usize {
4613        let mut indexed_vector_count = 0;
4614        for shard in self.shard_vec.iter() {
4615            indexed_vector_count += shard.read().await.indexed_vector_count;
4616        }
4617        indexed_vector_count
4618    }
4619
4620    /// Get number of indexed clusters.
4621    pub async fn indexed_cluster_count(&self) -> usize {
4622        let mut indexed_cluster_count = 0;
4623        for shard in self.shard_vec.iter() {
4624            indexed_cluster_count += shard.read().await.indexed_cluster_count;
4625        }
4626        indexed_cluster_count
4627    }
4628
4629    /// Get number of index levels. One index level comprises 64K documents **per shard**.
4630    pub async fn level_count(&self) -> usize {
4631        let mut level_count = 0;
4632        for shard in self.shard_vec.iter() {
4633            level_count += shard.read().await.level_index.len();
4634        }
4635        level_count
4636    }
4637
4638    /// Get number of index shards.
4639    pub async fn shard_count(&self) -> usize {
4640        self.shard_number
4641    }
4642
4643    /// Get number of facets defined in the index schema.
4644    pub fn facets_count(&self) -> usize {
4645        self.facets.len()
4646    }
4647
4648    /// get_index_facets_minmax: return map of numeric facet fields, each with field name and min/max values.
4649    pub async fn index_facets_minmax(&self) -> HashMap<String, MinMaxFieldJson> {
4650        let mut facets_minmax: HashMap<String, MinMaxFieldJson> = HashMap::new();
4651        for shard in self.shard_vec.iter() {
4652            for facet in shard.read().await.facets.iter() {
4653                match (&facet.min, &facet.max) {
4654                    (ValueType::U8(min), ValueType::U8(max)) => {
4655                        if let Some(item) = facets_minmax.get_mut(&facet.name) {
4656                            *item = MinMaxFieldJson {
4657                                min: (*min.min(&(item.min.as_u64().unwrap() as u8))).into(),
4658                                max: (*max.min(&(item.max.as_u64().unwrap() as u8))).into(),
4659                            }
4660                        } else {
4661                            facets_minmax.insert(
4662                                facet.name.clone(),
4663                                MinMaxFieldJson {
4664                                    min: (*min).into(),
4665                                    max: (*max).into(),
4666                                },
4667                            );
4668                        }
4669                    }
4670                    (ValueType::U16(min), ValueType::U16(max)) => {
4671                        if let Some(item) = facets_minmax.get_mut(&facet.name) {
4672                            *item = MinMaxFieldJson {
4673                                min: (*min.min(&(item.min.as_u64().unwrap() as u16))).into(),
4674                                max: (*max.min(&(item.max.as_u64().unwrap() as u16))).into(),
4675                            }
4676                        } else {
4677                            facets_minmax.insert(
4678                                facet.name.clone(),
4679                                MinMaxFieldJson {
4680                                    min: (*min).into(),
4681                                    max: (*max).into(),
4682                                },
4683                            );
4684                        }
4685                    }
4686                    (ValueType::U32(min), ValueType::U32(max)) => {
4687                        if let Some(item) = facets_minmax.get_mut(&facet.name) {
4688                            *item = MinMaxFieldJson {
4689                                min: (*min.min(&(item.min.as_u64().unwrap() as u32))).into(),
4690                                max: (*max.min(&(item.max.as_u64().unwrap() as u32))).into(),
4691                            }
4692                        } else {
4693                            facets_minmax.insert(
4694                                facet.name.clone(),
4695                                MinMaxFieldJson {
4696                                    min: (*min).into(),
4697                                    max: (*max).into(),
4698                                },
4699                            );
4700                        }
4701                    }
4702                    (ValueType::U64(min), ValueType::U64(max)) => {
4703                        if let Some(item) = facets_minmax.get_mut(&facet.name) {
4704                            *item = MinMaxFieldJson {
4705                                min: (*min.min(&(item.min.as_u64().unwrap()))).into(),
4706                                max: (*max.min(&(item.max.as_u64().unwrap()))).into(),
4707                            }
4708                        } else {
4709                            facets_minmax.insert(
4710                                facet.name.clone(),
4711                                MinMaxFieldJson {
4712                                    min: (*min).into(),
4713                                    max: (*max).into(),
4714                                },
4715                            );
4716                        }
4717                    }
4718                    (ValueType::I8(min), ValueType::I8(max)) => {
4719                        if let Some(item) = facets_minmax.get_mut(&facet.name) {
4720                            *item = MinMaxFieldJson {
4721                                min: (*min.min(&(item.min.as_i64().unwrap() as i8))).into(),
4722                                max: (*max.min(&(item.max.as_i64().unwrap() as i8))).into(),
4723                            }
4724                        } else {
4725                            facets_minmax.insert(
4726                                facet.name.clone(),
4727                                MinMaxFieldJson {
4728                                    min: (*min).into(),
4729                                    max: (*max).into(),
4730                                },
4731                            );
4732                        }
4733                    }
4734                    (ValueType::I16(min), ValueType::I16(max)) => {
4735                        if let Some(item) = facets_minmax.get_mut(&facet.name) {
4736                            *item = MinMaxFieldJson {
4737                                min: (*min.min(&(item.min.as_i64().unwrap() as i16))).into(),
4738                                max: (*max.min(&(item.max.as_i64().unwrap() as i16))).into(),
4739                            }
4740                        } else {
4741                            facets_minmax.insert(
4742                                facet.name.clone(),
4743                                MinMaxFieldJson {
4744                                    min: (*min).into(),
4745                                    max: (*max).into(),
4746                                },
4747                            );
4748                        }
4749                    }
4750                    (ValueType::I32(min), ValueType::I32(max)) => {
4751                        if let Some(item) = facets_minmax.get_mut(&facet.name) {
4752                            *item = MinMaxFieldJson {
4753                                min: (*min.min(&(item.min.as_i64().unwrap() as i32))).into(),
4754                                max: (*max.min(&(item.max.as_i64().unwrap() as i32))).into(),
4755                            }
4756                        } else {
4757                            facets_minmax.insert(
4758                                facet.name.clone(),
4759                                MinMaxFieldJson {
4760                                    min: (*min).into(),
4761                                    max: (*max).into(),
4762                                },
4763                            );
4764                        }
4765                    }
4766                    (ValueType::I64(min), ValueType::I64(max)) => {
4767                        if let Some(item) = facets_minmax.get_mut(&facet.name) {
4768                            *item = MinMaxFieldJson {
4769                                min: (*min.min(&(item.min.as_i64().unwrap()))).into(),
4770                                max: (*max.min(&(item.max.as_i64().unwrap()))).into(),
4771                            }
4772                        } else {
4773                            facets_minmax.insert(
4774                                facet.name.clone(),
4775                                MinMaxFieldJson {
4776                                    min: (*min).into(),
4777                                    max: (*max).into(),
4778                                },
4779                            );
4780                        }
4781                    }
4782                    (ValueType::Timestamp(min), ValueType::Timestamp(max)) => {
4783                        if let Some(item) = facets_minmax.get_mut(&facet.name) {
4784                            *item = MinMaxFieldJson {
4785                                min: (*min.min(&(item.min.as_i64().unwrap()))).into(),
4786                                max: (*max.min(&(item.max.as_i64().unwrap()))).into(),
4787                            }
4788                        } else {
4789                            facets_minmax.insert(
4790                                facet.name.clone(),
4791                                MinMaxFieldJson {
4792                                    min: (*min).into(),
4793                                    max: (*max).into(),
4794                                },
4795                            );
4796                        }
4797                    }
4798                    (ValueType::F32(min), ValueType::F32(max)) => {
4799                        if let Some(item) = facets_minmax.get_mut(&facet.name) {
4800                            *item = MinMaxFieldJson {
4801                                min: min.min(item.min.as_f64().unwrap() as f32).into(),
4802                                max: max.min(item.max.as_f64().unwrap() as f32).into(),
4803                            }
4804                        } else {
4805                            facets_minmax.insert(
4806                                facet.name.clone(),
4807                                MinMaxFieldJson {
4808                                    min: (*min).into(),
4809                                    max: (*max).into(),
4810                                },
4811                            );
4812                        }
4813                    }
4814                    (ValueType::F64(min), ValueType::F64(max)) => {
4815                        if let Some(item) = facets_minmax.get_mut(&facet.name) {
4816                            *item = MinMaxFieldJson {
4817                                min: min.min(item.min.as_f64().unwrap()).into(),
4818                                max: max.min(item.max.as_f64().unwrap()).into(),
4819                            }
4820                        } else {
4821                            facets_minmax.insert(
4822                                facet.name.clone(),
4823                                MinMaxFieldJson {
4824                                    min: (*min).into(),
4825                                    max: (*max).into(),
4826                                },
4827                            );
4828                        }
4829                    }
4830                    _ => {}
4831                }
4832            }
4833        }
4834        facets_minmax
4835    }
4836
4837    /// get_index_string_facets: list of string facet fields, each with field name and a map of unique values and their count (number of times the specific value appears in the whole index).
4838    /// values are sorted by their occurrence count within all indexed documents in descending order
4839    /// * `query_facets`: Must be set if facet fields should be returned in get_index_facets. If set to Vec::new() then no facet fields are returned.
4840    ///   The prefix property of a QueryFacet allows to filter the returned facet values to those matching a given prefix, if there are too many distinct values per facet field.
4841    ///   The length property of a QueryFacet allows limiting the number of returned distinct values per facet field, if there are too many distinct values.  The QueryFacet can be used to improve the usability in an UI.
4842    ///   If the length property of a QueryFacet is set to 0 then no facet values for that facet are returned.
4843    ///   The facet values are sorted by the frequency of the appearance of the value within the indexed documents matching the query in descending order.
4844    ///   Example: query_facets = vec![QueryFacet::String16 {field: "language".to_string(),prefix: "ger".to_string(),length: 5},QueryFacet::String16 {field: "brand".to_string(),prefix: "a".to_string(),length: 5}];
4845    pub async fn get_index_string_facets(
4846        &self,
4847        query_facets: Vec<QueryFacet>,
4848    ) -> Option<AHashMap<String, Facet>> {
4849        if self.facets.is_empty() {
4850            return None;
4851        }
4852
4853        let mut result: AHashMap<String, Facet> = AHashMap::new();
4854
4855        let mut result_facets: AHashMap<String, (AHashMap<String, usize>, u32)> = AHashMap::new();
4856        for query_facet in query_facets.iter() {
4857            match query_facet {
4858                QueryFacet::String16 {
4859                    field,
4860                    prefix: _,
4861                    length,
4862                } => {
4863                    result_facets.insert(field.into(), (AHashMap::new(), *length as u32));
4864                }
4865                QueryFacet::StringSet16 {
4866                    field,
4867                    prefix: _,
4868                    length,
4869                } => {
4870                    result_facets.insert(field.into(), (AHashMap::new(), *length as u32));
4871                }
4872
4873                QueryFacet::String32 {
4874                    field,
4875                    prefix: _,
4876                    length,
4877                } => {
4878                    result_facets.insert(field.into(), (AHashMap::new(), *length));
4879                }
4880                QueryFacet::StringSet32 {
4881                    field,
4882                    prefix: _,
4883                    length,
4884                } => {
4885                    result_facets.insert(field.into(), (AHashMap::new(), *length));
4886                }
4887
4888                _ => {}
4889            }
4890        }
4891
4892        for shard_arc in self.shard_vec.iter() {
4893            let shard = shard_arc.read().await;
4894            if !shard.facets.is_empty() {
4895                for facet in shard.facets.iter() {
4896                    if let Some(existing) = result_facets.get_mut(&facet.name) {
4897                        for (key, value) in facet.values.iter() {
4898                            *existing.0.entry(key.clone()).or_insert(0) += value.1;
4899                        }
4900                    };
4901                }
4902            }
4903        }
4904
4905        for (key, value) in result_facets.iter_mut() {
4906            let sum = value
4907                .0
4908                .iter()
4909                .sorted_unstable_by(|a, b| b.1.cmp(a.1))
4910                .map(|(a, c)| (a.clone(), *c))
4911                .take(value.1 as usize)
4912                .collect::<Vec<_>>();
4913            result.insert(key.clone(), sum);
4914        }
4915
4916        Some(result)
4917    }
4918
4919    /// Reset the index to empty, while maintaining the schema.
4920    pub async fn clear_index(&mut self) {
4921        let index_path = Path::new(&self.index_path_string);
4922        let _ = fs::remove_file(index_path.join(DICTIONARY_FILENAME));
4923        if let Some(spelling_correction) = self.meta.spelling_correction.as_ref() {
4924            self.symspell_option = Some(Arc::new(RwLock::new(SymSpell::new(
4925                spelling_correction.max_dictionary_edit_distance,
4926                spelling_correction.term_length_threshold.clone(),
4927                7,
4928                spelling_correction.count_threshold,
4929            ))));
4930        }
4931
4932        let _ = fs::remove_file(index_path.join(COMPLETIONS_FILENAME));
4933        if let Some(_query_completion) = self.meta.query_completion.as_ref() {
4934            self.completion_option = Some(Arc::new(RwLock::new(PruningRadixTrie::new())));
4935        }
4936
4937        let mut result_object_list = Vec::new();
4938        for shard in self.shard_vec.iter() {
4939            let shard_clone = shard.clone();
4940            result_object_list.push(tokio::spawn(async move {
4941                shard_clone.write().await.clear_shard().await;
4942            }));
4943        }
4944        future::join_all(result_object_list).await;
4945    }
4946
4947    /// Delete index from disc and ram
4948    pub fn delete_index(&mut self) {
4949        let index_path = Path::new(&self.index_path_string);
4950
4951        let _ = fs::remove_dir_all(index_path);
4952    }
4953
4954    /// Get synonyms from index
4955    pub fn get_synonyms(&self) -> Result<Vec<Synonym>, String> {
4956        if let Ok(synonym_file) =
4957            File::open(Path::new(&self.index_path_string).join(SYNONYMS_FILENAME))
4958        {
4959            if let Ok(synonyms) = serde_json::from_reader(BufReader::new(synonym_file)) {
4960                Ok(synonyms)
4961            } else {
4962                Err("not found".into())
4963            }
4964        } else {
4965            Err("not found".into())
4966        }
4967    }
4968
4969    /// Set/replace/overwrite synonyms in index
4970    /// Affects only subsequently indexed documents
4971    pub fn set_synonyms(&mut self, synonyms: &Vec<Synonym>) -> Result<usize, String> {
4972        serde_json::to_writer(
4973            &File::create(Path::new(&self.index_path_string).join(SYNONYMS_FILENAME)).unwrap(),
4974            &synonyms,
4975        )
4976        .unwrap();
4977
4978        self.synonyms_map = get_synonyms_map(synonyms, self.segment_number_mask1);
4979        Ok(synonyms.len())
4980    }
4981
4982    /// Add/append/update/merge synonyms in index
4983    /// Affects only subsequently indexed documents
4984    pub fn add_synonyms(&mut self, synonyms: &[Synonym]) -> Result<usize, String> {
4985        let mut merged_synonyms = if let Ok(synonym_file) =
4986            File::open(Path::new(&self.index_path_string).join(SYNONYMS_FILENAME))
4987        {
4988            serde_json::from_reader(BufReader::new(synonym_file)).unwrap_or_default()
4989        } else {
4990            Vec::new()
4991        };
4992
4993        merged_synonyms.extend(synonyms.iter().cloned());
4994
4995        serde_json::to_writer(
4996            &File::create(Path::new(&self.index_path_string).join(SYNONYMS_FILENAME)).unwrap(),
4997            &merged_synonyms,
4998        )
4999        .unwrap();
5000
5001        self.synonyms_map = get_synonyms_map(&merged_synonyms, self.segment_number_mask1);
5002        Ok(merged_synonyms.len())
5003    }
5004}
5005
5006/// Remove index from RAM (Reverse of open_index)
5007#[allow(async_fn_in_trait)]
5008pub trait Close {
5009    /// Remove index from RAM (Reverse of open_index)
5010    async fn close(&self);
5011}
5012
5013/// Remove index from RAM (Reverse of open_index)
5014impl Close for IndexArc {
5015    /// Remove index from RAM (Reverse of open_index)
5016    async fn close(&self) {
5017        self.commit().await;
5018
5019        let mut modified = false;
5020        for shard in self.read().await.shard_vec.iter() {
5021            if shard.read().await.modified {
5022                modified = true;
5023                break;
5024            }
5025        }
5026
5027        let mut dictionary_source = false;
5028        let mut completion_source = false;
5029        if modified {
5030            for schema_item in self.read().await.schema_map.iter() {
5031                if schema_item.1.dictionary_source {
5032                    dictionary_source = true;
5033                }
5034                if schema_item.1.completion_source {
5035                    completion_source = true;
5036                }
5037            }
5038        }
5039
5040        if completion_source
5041            && let Some(completion_option) = &self.read().await.completion_option.as_ref()
5042        {
5043            let trie = completion_option.read().await;
5044            let completions_path =
5045                Path::new(&self.read().await.index_path_string).join(COMPLETIONS_FILENAME);
5046
5047            _ = trie.save_completions(&completions_path, ":");
5048        }
5049
5050        if dictionary_source && let Some(symspell) = &mut self.read().await.symspell_option.as_ref()
5051        {
5052            let dictionary_path =
5053                Path::new(&self.read().await.index_path_string).join(DICTIONARY_FILENAME);
5054            let _ = symspell.read().await.save_dictionary(&dictionary_path, " ");
5055        }
5056
5057        let mut result_object_list = Vec::new();
5058        for shard in self.read().await.shard_vec.iter() {
5059            let shard_clone = shard.clone();
5060            result_object_list.push(tokio::spawn(async move {
5061                let mut mmap_options = MmapOptions::new();
5062                let mmap: MmapMut = mmap_options.len(4).map_anon().unwrap();
5063                shard_clone.write().await.index_file_mmap = mmap
5064                    .make_read_only()
5065                    .expect("Unable to make Mmap read-only");
5066
5067                let mut mmap_options = MmapOptions::new();
5068                let mmap: MmapMut = mmap_options.len(4).map_anon().unwrap();
5069                shard_clone.write().await.docstore_file_mmap = mmap
5070                    .make_read_only()
5071                    .expect("Unable to make Mmap read-only");
5072
5073                shard_clone.write().await.index_option = None;
5074            }));
5075        }
5076        future::join_all(result_object_list).await;
5077    }
5078}
5079
5080/// Delete document from index by document id
5081/// ⚠️ Use search or get_iterator first to obtain a valid doc_id. Document IDs are not guaranteed to be continuous and gapless!
5082#[allow(async_fn_in_trait)]
5083pub trait DeleteDocument {
5084    /// Delete document from index by document id
5085    async fn delete_document(&self, docid: u64);
5086}
5087
5088/// Delete document from index by document id
5089///
5090/// Arguments:
5091/// * `doc_id`: Document ID that specifies which document to delete from the index.
5092///   ⚠️ Use search or get_iterator first to obtain a valid doc_id. Document IDs are not guaranteed to be continuous and gapless!
5093///
5094/// Immediately effective, independent of commit.
5095/// Index space used by deleted documents is not reclaimed (until compaction is implemented), but result_count_total is updated.
5096/// By manually deleting the delete.bin file the deleted documents can be recovered (until compaction).
5097/// Deleted documents impact performance, especially but not limited to counting (Count, TopKCount). They also increase the size of the index (until compaction is implemented).
5098/// For minimal query latency delete index and reindexing documents is preferred over deleting documents (until compaction is implemented).
5099/// BM25 scores are not updated (until compaction is implemented), but the impact is minimal.
5100impl DeleteDocument for IndexArc {
5101    async fn delete_document(&self, docid: u64) {
5102        let index_ref = self.read().await;
5103        let shard_number = index_ref.shard_number as u64;
5104        let shard_id = docid % shard_number;
5105        let doc_id = docid / shard_number;
5106
5107        let mut shard_mut = index_ref.shard_vec[shard_id as usize].write().await;
5108
5109        if doc_id as usize >= shard_mut.indexed_doc_count {
5110            return;
5111        }
5112        if shard_mut.delete_hashset.insert(doc_id as usize) {
5113            let mut buffer: [u8; 8] = [0; 8];
5114            write_u64(doc_id, &mut buffer, 0);
5115            let _ = shard_mut.delete_file.write(&buffer);
5116            let _ = shard_mut.delete_file.flush();
5117        }
5118    }
5119}
5120
5121/// Delete documents from index by document id
5122#[allow(async_fn_in_trait)]
5123pub trait DeleteDocuments {
5124    /// Delete documents from index by document id
5125    async fn delete_documents(&self, docid_vec: Vec<u64>);
5126}
5127
5128/// Delete documents from index by document id
5129/// Document ID can by obtained by search.
5130/// Immediately effective, independent of commit.
5131/// Index space used by deleted documents is not reclaimed (until compaction is implemented), but result_count_total is updated.
5132/// By manually deleting the delete.bin file the deleted documents can be recovered (until compaction).
5133/// Deleted documents impact performance, especially but not limited to counting (Count, TopKCount). They also increase the size of the index (until compaction is implemented).
5134/// For minimal query latency delete index and reindexing documents is preferred over deleting documents (until compaction is implemented).
5135/// BM25 scores are not updated (until compaction is implemented), but the impact is minimal.
5136impl DeleteDocuments for IndexArc {
5137    async fn delete_documents(&self, docid_vec: Vec<u64>) {
5138        for docid in docid_vec {
5139            self.delete_document(docid).await;
5140        }
5141    }
5142}
5143
5144/// Delete documents from index by query
5145/// Delete and search have identical parameters.
5146/// It is recommended to test with search prior to delete to verify that only those documents are returned that you really want to delete.
5147#[allow(clippy::too_many_arguments)]
5148#[allow(async_fn_in_trait)]
5149pub trait DeleteDocumentsByQuery {
5150    /// Delete documents from index by query
5151    /// Delete and search have identical parameters.
5152    /// It is recommended to test with search prior to delete to verify that only those documents are returned that you really want to delete.
5153    async fn delete_documents_by_query(
5154        &self,
5155        query_string: String,
5156        query_type_default: QueryType,
5157        offset: usize,
5158        length: usize,
5159        include_uncommitted: bool,
5160        field_filter: Vec<String>,
5161        facet_filter: Vec<FacetFilter>,
5162        result_sort: Vec<ResultSort>,
5163    );
5164}
5165
5166/// Delete documents from index by query
5167/// Delete and search have identical parameters.
5168/// It is recommended to test with search prior to delete to verify that only those documents are returned that you really want to delete.
5169impl DeleteDocumentsByQuery for IndexArc {
5170    async fn delete_documents_by_query(
5171        &self,
5172        query_string: String,
5173        query_type_default: QueryType,
5174        offset: usize,
5175        length: usize,
5176        include_uncommitted: bool,
5177        field_filter: Vec<String>,
5178        facet_filter: Vec<FacetFilter>,
5179        result_sort: Vec<ResultSort>,
5180    ) {
5181        let rlo = self
5182            .search(
5183                query_string.to_owned(),
5184                None,
5185                query_type_default,
5186                SearchMode::Lexical,
5187                false,
5188                offset,
5189                length,
5190                ResultType::Topk,
5191                include_uncommitted,
5192                field_filter,
5193                Vec::new(),
5194                facet_filter,
5195                result_sort,
5196                QueryRewriting::SearchOnly,
5197            )
5198            .await;
5199
5200        let document_id_vec: Vec<u64> = rlo
5201            .results
5202            .iter()
5203            .map(|result| result.doc_id as u64)
5204            .collect();
5205        self.delete_documents(document_id_vec).await;
5206    }
5207}
5208
5209/// Update document in index
5210/// Update_document is a combination of delete_document and index_document.
5211/// All current limitations of delete_document apply.
5212#[allow(async_fn_in_trait)]
5213pub trait UpdateDocument {
5214    /// Update document in index
5215    /// Update_document is a combination of delete_document and index_document.
5216    /// All current limitations of delete_document apply.
5217    async fn update_document(&self, id_document: (u64, Document));
5218}
5219
5220/// Update document in index
5221/// Update_document is a combination of delete_document and index_document.
5222/// All current limitations of delete_document apply.
5223impl UpdateDocument for IndexArc {
5224    async fn update_document(&self, id_document: (u64, Document)) {
5225        self.delete_document(id_document.0).await;
5226        self.index_document(id_document.1, FileType::None).await;
5227    }
5228}
5229
5230/// Update documents in index
5231/// Update_document is a combination of delete_document and index_document.
5232/// All current limitations of delete_document apply.
5233#[allow(async_fn_in_trait)]
5234pub trait UpdateDocuments {
5235    /// Update documents in index
5236    /// Update_document is a combination of delete_document and index_document.
5237    /// All current limitations of delete_document apply.
5238    async fn update_documents(&self, id_document_vec: Vec<(u64, Document)>);
5239}
5240
5241/// Update documents in index
5242/// Update_document is a combination of delete_document and index_document.
5243/// All current limitations of delete_document apply.
5244impl UpdateDocuments for IndexArc {
5245    async fn update_documents(&self, id_document_vec: Vec<(u64, Document)>) {
5246        let (docid_vec, document_vec): (Vec<_>, Vec<_>) = id_document_vec.into_iter().unzip();
5247        self.delete_documents(docid_vec).await;
5248        self.index_documents(document_vec).await;
5249    }
5250}
5251
5252/// Indexes a list of documents
5253#[allow(async_fn_in_trait)]
5254pub trait IndexDocuments {
5255    /// Indexes a list of documents
5256    /// May block, if the threshold of documents indexed in parallel is exceeded.
5257    async fn index_documents(&self, document_vec: Vec<Document>);
5258}
5259
5260impl IndexDocuments for IndexArc {
5261    /// Index list of documents (bulk)
5262    /// May block, if the threshold of documents indexed in parallel is exceeded.
5263    async fn index_documents(&self, document_vec: Vec<Document>) {
5264        for document in document_vec {
5265            self.index_document(document, FileType::None).await;
5266        }
5267    }
5268}
5269
5270/// Indexes a single document
5271#[allow(async_fn_in_trait)]
5272pub trait IndexDocument {
5273    /// Indexes a single document
5274    /// May block, if the threshold of documents indexed in parallel is exceeded.
5275    async fn index_document(&self, document: Document, file: FileType);
5276}
5277
5278impl IndexDocument for IndexArc {
5279    /// Index document
5280    /// May block, if the threshold of documents indexed in parallel is exceeded.
5281    async fn index_document(&self, document: Document, file: FileType) {
5282        let shard_number = self.read().await.shard_number;
5283        let docid_global_arc = self.read().await.docid_global.clone();
5284        let mut docid_global = docid_global_arc.write().await;
5285        let docid_global_clone = *docid_global;
5286        let shard_id = *docid_global % shard_number;
5287
5288        let shard_arc = self.read().await.shard_vec[shard_id].clone();
5289        let semaphore = shard_arc.read().await.semaphore.clone();
5290        let permit = semaphore.acquire_owned().await.unwrap();
5291
5292        *docid_global += 1;
5293        drop(docid_global);
5294
5295        INDEX_RUNTIME.handle().spawn(async move {
5296            shard_arc
5297                .index_document_shard(document, file, docid_global_clone)
5298                .await;
5299            drop(permit);
5300        });
5301    }
5302}
5303
5304/// Indexes a single document
5305#[allow(async_fn_in_trait)]
5306pub(crate) trait IndexDocumentShard {
5307    /// Indexes a single document
5308    /// May block, if the threshold of documents indexed in parallel is exceeded.
5309    async fn index_document_shard(&self, document: Document, file: FileType, docid_global: usize);
5310}
5311
5312/// Recursively extract strings from a serde_json::Value and append them to a vector of Strings.
5313/// Only value, not field names.
5314pub(crate) fn object_values_to_string_vec_recursive(value: &Value, out: &mut Vec<String>) {
5315    match value {
5316        Value::String(s) => out.push(s.clone()),
5317        Value::Array(arr) => {
5318            for v in arr {
5319                object_values_to_string_vec_recursive(v, out);
5320            }
5321        }
5322        Value::Object(map) => {
5323            for v in map.values() {
5324                object_values_to_string_vec_recursive(v, out);
5325            }
5326        }
5327        _ => {}
5328    }
5329}
5330
5331impl IndexDocumentShard for ShardArc {
5332    /// Index document
5333    /// May block, if the threshold of documents indexed in parallel is exceeded.
5334    async fn index_document_shard(&self, document: Document, file: FileType, docid_global: usize) {
5335        let shard_arc_clone = self.clone();
5336        let shard_ref = self.read().await;
5337        let schema = shard_ref.indexed_schema_vec.clone();
5338        let ngram_indexing = shard_ref.meta.ngram_indexing;
5339        let indexed_field_vec_len = shard_ref.indexed_field_vec.len();
5340        let tokenizer_type = shard_ref.meta.tokenizer;
5341        let segment_number_mask1 = shard_ref.segment_number_mask1;
5342
5343        drop(shard_ref);
5344
5345        let token_per_field_max: u32 = u16::MAX as u32;
5346        let mut unique_terms: AHashMap<String, TermObject> = AHashMap::new();
5347        let mut field_vec: Vec<(usize, u8, u32, u32)> = Vec::new();
5348
5349        let shard_ref2 = shard_arc_clone.read().await;
5350
5351        for schema_field in schema.iter() {
5352            if !schema_field.index_lexical {
5353                continue;
5354            }
5355
5356            if let Some(field_value) = document.get(&schema_field.field) {
5357                let mut non_unique_terms: Vec<NonUniqueTermObject> = Vec::new();
5358                let mut nonunique_terms_count = 0u32;
5359
5360                let text = match schema_field.field_type {
5361                    FieldType::Json => {
5362                        if matches!(field_value, Value::Object { .. }) {
5363                            let mut strings_vec: Vec<String> = Vec::new();
5364                            object_values_to_string_vec_recursive(field_value, &mut strings_vec);
5365                            strings_vec.join(" ")
5366                        } else {
5367                            serde_json::from_value::<String>(field_value.clone())
5368                                .unwrap_or(field_value.to_string())
5369                        }
5370                    }
5371                    FieldType::Text | FieldType::String16 | FieldType::String32 => {
5372                        serde_json::from_value::<String>(field_value.clone())
5373                            .unwrap_or(field_value.to_string())
5374                    }
5375
5376                    _ => field_value.to_string(),
5377                };
5378
5379                let mut query_type_mut = QueryType::Union;
5380
5381                tokenizer(
5382                    &shard_ref2,
5383                    &text,
5384                    &mut unique_terms,
5385                    &mut non_unique_terms,
5386                    tokenizer_type,
5387                    segment_number_mask1,
5388                    &mut nonunique_terms_count,
5389                    token_per_field_max,
5390                    MAX_POSITIONS_PER_TERM,
5391                    false,
5392                    &mut query_type_mut,
5393                    ngram_indexing,
5394                    schema_field.indexed_field_id,
5395                    indexed_field_vec_len,
5396                )
5397                .await;
5398
5399                let document_length_compressed: u8 = int_to_byte4(nonunique_terms_count);
5400                let document_length_normalized: u32 =
5401                    DOCUMENT_LENGTH_COMPRESSION[document_length_compressed as usize];
5402                field_vec.push((
5403                    schema_field.indexed_field_id,
5404                    document_length_compressed,
5405                    document_length_normalized,
5406                    nonunique_terms_count,
5407                ));
5408            }
5409        }
5410        drop(shard_ref2);
5411
5412        let ngrams: Vec<String> = unique_terms
5413            .iter()
5414            .filter(|term| term.1.ngram_type != NgramType::SingleTerm)
5415            .map(|term| term.1.term.clone())
5416            .collect();
5417
5418        for term in ngrams.iter() {
5419            let ngram = unique_terms.get(term).unwrap();
5420
5421            match ngram.ngram_type {
5422                NgramType::SingleTerm => {}
5423                NgramType::NgramFF | NgramType::NgramFR | NgramType::NgramRF => {
5424                    let term_ngram1 = ngram.term_ngram_1.clone();
5425                    let term_ngram2 = ngram.term_ngram_0.clone();
5426
5427                    for indexed_field_id in 0..indexed_field_vec_len {
5428                        let positions_count_ngram1 =
5429                            unique_terms[&term_ngram1].field_positions_vec[indexed_field_id].len();
5430                        let positions_count_ngram2 =
5431                            unique_terms[&term_ngram2].field_positions_vec[indexed_field_id].len();
5432                        let ngram = unique_terms.get_mut(term).unwrap();
5433
5434                        if positions_count_ngram1 > 0 {
5435                            ngram
5436                                .field_vec_ngram1
5437                                .push((indexed_field_id, positions_count_ngram1 as u32));
5438                        }
5439                        if positions_count_ngram2 > 0 {
5440                            ngram
5441                                .field_vec_ngram2
5442                                .push((indexed_field_id, positions_count_ngram2 as u32));
5443                        }
5444                    }
5445                }
5446                _ => {
5447                    let term_ngram1 = ngram.term_ngram_2.clone();
5448                    let term_ngram2 = ngram.term_ngram_1.clone();
5449                    let term_ngram3 = ngram.term_ngram_0.clone();
5450
5451                    for indexed_field_id in 0..indexed_field_vec_len {
5452                        let positions_count_ngram1 =
5453                            unique_terms[&term_ngram1].field_positions_vec[indexed_field_id].len();
5454                        let positions_count_ngram2 =
5455                            unique_terms[&term_ngram2].field_positions_vec[indexed_field_id].len();
5456                        let positions_count_ngram3 =
5457                            unique_terms[&term_ngram3].field_positions_vec[indexed_field_id].len();
5458                        let ngram = unique_terms.get_mut(term).unwrap();
5459
5460                        if positions_count_ngram1 > 0 {
5461                            ngram
5462                                .field_vec_ngram1
5463                                .push((indexed_field_id, positions_count_ngram1 as u32));
5464                        }
5465                        if positions_count_ngram2 > 0 {
5466                            ngram
5467                                .field_vec_ngram2
5468                                .push((indexed_field_id, positions_count_ngram2 as u32));
5469                        }
5470                        if positions_count_ngram3 > 0 {
5471                            ngram
5472                                .field_vec_ngram3
5473                                .push((indexed_field_id, positions_count_ngram3 as u32));
5474                        }
5475                    }
5476                }
5477            }
5478        }
5479
5480        let document_item = DocumentItem {
5481            document,
5482            unique_terms,
5483            field_vec,
5484        };
5485
5486        shard_arc_clone
5487            .index_document_shard_2(document_item, file, docid_global)
5488            .await;
5489    }
5490}
5491
5492#[allow(async_fn_in_trait)]
5493pub(crate) trait IndexDocumentShard2 {
5494    async fn index_document_shard_2(
5495        &self,
5496        document_item: DocumentItem,
5497        file: FileType,
5498        docid_global: usize,
5499    );
5500}
5501
5502impl IndexDocumentShard2 for ShardArc {
5503    async fn index_document_shard_2(
5504        &self,
5505        document_item: DocumentItem,
5506        file: FileType,
5507        docid_global: usize,
5508    ) {
5509        let mut shard_mut = self.write().await;
5510
5511        let docid_local = docid_global / shard_mut.shard_number;
5512
5513        shard_mut.indexed_doc_count = docid_local + 1;
5514
5515        let do_commit = shard_mut.block_id != docid_local >> 16;
5516        if do_commit {
5517            if shard_mut.is_vector_indexing {
5518                shard_mut.commit_vector_shard().await;
5519            }
5520            shard_mut.commit_lexical_shard(docid_local).await;
5521
5522            shard_mut.block_id = docid_local >> 16;
5523        }
5524
5525        if shard_mut.is_vector_indexing {
5526            shard_mut
5527                .index_vector_shard(docid_local, &document_item.document)
5528                .await;
5529        }
5530
5531        if !shard_mut.facets.is_empty() {
5532            let facets_size_sum = shard_mut.facets_size_sum;
5533            for i in 0..shard_mut.facets.len() {
5534                let facet = &mut shard_mut.facets[i];
5535                if let Some(field_value) = document_item.document.get(&facet.name) {
5536                    let address = (facets_size_sum * docid_local) + facet.offset;
5537
5538                    match facet.field_type {
5539                        FieldType::U8 => {
5540                            let value = field_value.as_u64().unwrap_or_default() as u8;
5541                            match (&facet.min, &facet.max) {
5542                                (ValueType::U8(min), ValueType::U8(max)) => {
5543                                    if value < *min {
5544                                        facet.min = ValueType::U8(value);
5545                                    }
5546                                    if value > *max {
5547                                        facet.max = ValueType::U8(value);
5548                                    }
5549                                }
5550                                (ValueType::None, ValueType::None) => {
5551                                    facet.min = ValueType::U8(value);
5552                                    facet.max = ValueType::U8(value);
5553                                }
5554                                _ => {}
5555                            }
5556                            shard_mut.facets_file_mmap[address] = value
5557                        }
5558                        FieldType::U16 => {
5559                            let value = field_value.as_u64().unwrap_or_default() as u16;
5560                            match (&facet.min, &facet.max) {
5561                                (ValueType::U16(min), ValueType::U16(max)) => {
5562                                    if value < *min {
5563                                        facet.min = ValueType::U16(value);
5564                                    }
5565                                    if value > *max {
5566                                        facet.max = ValueType::U16(value);
5567                                    }
5568                                }
5569                                (ValueType::None, ValueType::None) => {
5570                                    facet.min = ValueType::U16(value);
5571                                    facet.max = ValueType::U16(value);
5572                                }
5573                                _ => {}
5574                            }
5575                            write_u16(value, &mut shard_mut.facets_file_mmap, address)
5576                        }
5577                        FieldType::U32 => {
5578                            let value = field_value.as_u64().unwrap_or_default() as u32;
5579                            match (&facet.min, &facet.max) {
5580                                (ValueType::U32(min), ValueType::U32(max)) => {
5581                                    if value < *min {
5582                                        facet.min = ValueType::U32(value);
5583                                    }
5584                                    if value > *max {
5585                                        facet.max = ValueType::U32(value);
5586                                    }
5587                                }
5588                                (ValueType::None, ValueType::None) => {
5589                                    facet.min = ValueType::U32(value);
5590                                    facet.max = ValueType::U32(value);
5591                                }
5592                                _ => {}
5593                            }
5594                            write_u32(value, &mut shard_mut.facets_file_mmap, address)
5595                        }
5596                        FieldType::U64 => {
5597                            let value = field_value.as_u64().unwrap_or_default();
5598                            match (&facet.min, &facet.max) {
5599                                (ValueType::U64(min), ValueType::U64(max)) => {
5600                                    if value < *min {
5601                                        facet.min = ValueType::U64(value);
5602                                    }
5603                                    if value > *max {
5604                                        facet.max = ValueType::U64(value);
5605                                    }
5606                                }
5607                                (ValueType::None, ValueType::None) => {
5608                                    facet.min = ValueType::U64(value);
5609                                    facet.max = ValueType::U64(value);
5610                                }
5611                                _ => {}
5612                            }
5613                            write_u64(value, &mut shard_mut.facets_file_mmap, address)
5614                        }
5615                        FieldType::I8 => {
5616                            let value = field_value.as_i64().unwrap_or_default() as i8;
5617                            match (&facet.min, &facet.max) {
5618                                (ValueType::I8(min), ValueType::I8(max)) => {
5619                                    if value < *min {
5620                                        facet.min = ValueType::I8(value);
5621                                    }
5622                                    if value > *max {
5623                                        facet.max = ValueType::I8(value);
5624                                    }
5625                                }
5626                                (ValueType::None, ValueType::None) => {
5627                                    facet.min = ValueType::I8(value);
5628                                    facet.max = ValueType::I8(value);
5629                                }
5630                                _ => {}
5631                            }
5632                            write_i8(value, &mut shard_mut.facets_file_mmap, address)
5633                        }
5634                        FieldType::I16 => {
5635                            let value = field_value.as_i64().unwrap_or_default() as i16;
5636                            match (&facet.min, &facet.max) {
5637                                (ValueType::I16(min), ValueType::I16(max)) => {
5638                                    if value < *min {
5639                                        facet.min = ValueType::I16(value);
5640                                    }
5641                                    if value > *max {
5642                                        facet.max = ValueType::I16(value);
5643                                    }
5644                                }
5645                                (ValueType::None, ValueType::None) => {
5646                                    facet.min = ValueType::I16(value);
5647                                    facet.max = ValueType::I16(value);
5648                                }
5649                                _ => {}
5650                            }
5651                            write_i16(value, &mut shard_mut.facets_file_mmap, address)
5652                        }
5653                        FieldType::I32 => {
5654                            let value = field_value.as_i64().unwrap_or_default() as i32;
5655                            match (&facet.min, &facet.max) {
5656                                (ValueType::I32(min), ValueType::I32(max)) => {
5657                                    if value < *min {
5658                                        facet.min = ValueType::I32(value);
5659                                    }
5660                                    if value > *max {
5661                                        facet.max = ValueType::I32(value);
5662                                    }
5663                                }
5664                                (ValueType::None, ValueType::None) => {
5665                                    facet.min = ValueType::I32(value);
5666                                    facet.max = ValueType::I32(value);
5667                                }
5668                                _ => {}
5669                            }
5670                            write_i32(value, &mut shard_mut.facets_file_mmap, address)
5671                        }
5672                        FieldType::I64 => {
5673                            let value = field_value.as_i64().unwrap_or_default();
5674                            match (&facet.min, &facet.max) {
5675                                (ValueType::I64(min), ValueType::I64(max)) => {
5676                                    if value < *min {
5677                                        facet.min = ValueType::I64(value);
5678                                    }
5679                                    if value > *max {
5680                                        facet.max = ValueType::I64(value);
5681                                    }
5682                                }
5683                                (ValueType::None, ValueType::None) => {
5684                                    facet.min = ValueType::I64(value);
5685                                    facet.max = ValueType::I64(value);
5686                                }
5687                                _ => {}
5688                            }
5689                            write_i64(value, &mut shard_mut.facets_file_mmap, address)
5690                        }
5691                        FieldType::Timestamp => {
5692                            let value = field_value.as_i64().unwrap_or_default();
5693                            match (&facet.min, &facet.max) {
5694                                (ValueType::Timestamp(min), ValueType::Timestamp(max)) => {
5695                                    if value < *min {
5696                                        facet.min = ValueType::Timestamp(value);
5697                                    }
5698                                    if value > *max {
5699                                        facet.max = ValueType::Timestamp(value);
5700                                    }
5701                                }
5702                                (ValueType::None, ValueType::None) => {
5703                                    facet.min = ValueType::Timestamp(value);
5704                                    facet.max = ValueType::Timestamp(value);
5705                                }
5706                                _ => {}
5707                            }
5708
5709                            write_i64(value, &mut shard_mut.facets_file_mmap, address);
5710                        }
5711                        FieldType::F32 => {
5712                            let value = field_value.as_f64().unwrap_or_default() as f32;
5713                            match (&facet.min, &facet.max) {
5714                                (ValueType::F32(min), ValueType::F32(max)) => {
5715                                    if value < *min {
5716                                        facet.min = ValueType::F32(value);
5717                                    }
5718                                    if value > *max {
5719                                        facet.max = ValueType::F32(value);
5720                                    }
5721                                }
5722                                (ValueType::None, ValueType::None) => {
5723                                    facet.min = ValueType::F32(value);
5724                                    facet.max = ValueType::F32(value);
5725                                }
5726                                _ => {}
5727                            }
5728
5729                            write_f32(value, &mut shard_mut.facets_file_mmap, address)
5730                        }
5731                        FieldType::F64 => {
5732                            let value = field_value.as_f64().unwrap_or_default();
5733                            match (&facet.min, &facet.max) {
5734                                (ValueType::F64(min), ValueType::F64(max)) => {
5735                                    if value < *min {
5736                                        facet.min = ValueType::F64(value);
5737                                    }
5738                                    if value > *max {
5739                                        facet.max = ValueType::F64(value);
5740                                    }
5741                                }
5742                                (ValueType::None, ValueType::None) => {
5743                                    facet.min = ValueType::F64(value);
5744                                    facet.max = ValueType::F64(value);
5745                                }
5746                                _ => {}
5747                            }
5748
5749                            write_f64(value, &mut shard_mut.facets_file_mmap, address)
5750                        }
5751                        FieldType::String16 if facet.values.len() < u16::MAX as usize => {
5752                            let key = serde_json::from_value::<String>(field_value.clone())
5753                                .unwrap_or(field_value.to_string());
5754
5755                            let key_string = key.clone();
5756                            let key = vec![key];
5757
5758                            facet.values.entry(key_string.clone()).or_insert((key, 0)).1 += 1;
5759
5760                            let facet_value_id =
5761                                facet.values.get_index_of(&key_string).unwrap() as u16;
5762                            write_u16(facet_value_id, &mut shard_mut.facets_file_mmap, address)
5763                        }
5764
5765                        FieldType::StringSet16 if facet.values.len() < u16::MAX as usize => {
5766                            let mut key: Vec<String> =
5767                                serde_json::from_value(field_value.clone()).unwrap();
5768                            key.sort();
5769
5770                            let key_string = key.join("_");
5771                            facet.values.entry(key_string.clone()).or_insert((key, 0)).1 += 1;
5772
5773                            let facet_value_id =
5774                                facet.values.get_index_of(&key_string).unwrap() as u16;
5775                            write_u16(facet_value_id, &mut shard_mut.facets_file_mmap, address)
5776                        }
5777
5778                        FieldType::String32 if facet.values.len() < u32::MAX as usize => {
5779                            let key = serde_json::from_value::<String>(field_value.clone())
5780                                .unwrap_or(field_value.to_string());
5781
5782                            let key_string = key.clone();
5783                            let key = vec![key];
5784
5785                            facet.values.entry(key_string.clone()).or_insert((key, 0)).1 += 1;
5786
5787                            let facet_value_id =
5788                                facet.values.get_index_of(&key_string).unwrap() as u32;
5789                            write_u32(facet_value_id, &mut shard_mut.facets_file_mmap, address)
5790                        }
5791
5792                        FieldType::StringSet32 if facet.values.len() < u32::MAX as usize => {
5793                            let mut key: Vec<String> =
5794                                serde_json::from_value(field_value.clone()).unwrap();
5795                            key.sort();
5796
5797                            let key_string = key.join("_");
5798                            facet.values.entry(key_string.clone()).or_insert((key, 0)).1 += 1;
5799
5800                            let facet_value_id =
5801                                facet.values.get_index_of(&key_string).unwrap() as u32;
5802                            write_u32(facet_value_id, &mut shard_mut.facets_file_mmap, address)
5803                        }
5804
5805                        FieldType::Point => {
5806                            if let Ok(point) = serde_json::from_value::<Point>(field_value.clone())
5807                                && point.len() == 2
5808                            {
5809                                if point[0] >= -90.0
5810                                    && point[0] <= 90.0
5811                                    && point[1] >= -180.0
5812                                    && point[1] <= 180.0
5813                                {
5814                                    let morton_code = encode_morton_2_d(&point);
5815                                    write_u64(morton_code, &mut shard_mut.facets_file_mmap, address)
5816                                } else {
5817                                    println!(
5818                                        "outside valid coordinate range: {} {}",
5819                                        point[0], point[1]
5820                                    );
5821                                }
5822                            }
5823                        }
5824
5825                        _ => {}
5826                    };
5827                }
5828            }
5829        }
5830
5831        if !shard_mut.uncommitted {
5832            if shard_mut.segments_level0[0].positions_compressed.is_empty() {
5833                for strip0 in shard_mut.segments_level0.iter_mut() {
5834                    strip0.positions_compressed = vec![0; MAX_POSITIONS_PER_TERM * 2];
5835                }
5836            }
5837            shard_mut.uncommitted = true;
5838        }
5839
5840        let mut longest_field_id: usize = 0;
5841        let mut longest_field_length: u32 = 0;
5842        for value in document_item.field_vec {
5843            if docid_local == 0 && value.3 > longest_field_length {
5844                longest_field_id = value.0;
5845                longest_field_length = value.3;
5846            }
5847
5848            shard_mut.document_length_compressed_array[value.0]
5849                [docid_local & 0b11111111_11111111] = value.1;
5850            shard_mut.positions_sum_normalized += value.2 as u64;
5851            shard_mut.indexed_field_vec[value.0].field_length_sum += value.2 as usize;
5852        }
5853
5854        if docid_local == 0 && shard_mut.is_lexical_indexing {
5855            if !shard_mut.longest_field_auto {
5856                longest_field_id = shard_mut.longest_field_id;
5857            }
5858            shard_mut.longest_field_id = longest_field_id;
5859            shard_mut.indexed_field_vec[longest_field_id].is_longest_field = true;
5860            if shard_mut.longest_field_auto && shard_mut.indexed_field_vec.len() > 1 {
5861                println!(
5862                    "detect longest field id {} name {} length {}",
5863                    longest_field_id,
5864                    shard_mut.indexed_field_vec[longest_field_id].schema_field_name,
5865                    longest_field_length
5866                );
5867            }
5868        }
5869
5870        let mut unique_terms = document_item.unique_terms;
5871        if !shard_mut.synonyms_map.is_empty() {
5872            let unique_terms_clone = unique_terms.clone();
5873            for term in unique_terms_clone.iter() {
5874                if term.1.ngram_type == NgramType::SingleTerm {
5875                    let synonym = shard_mut.synonyms_map.get(&term.1.key_hash).cloned();
5876                    if let Some(synonym) = synonym {
5877                        for synonym_term in synonym {
5878                            let mut term_clone = term.1.clone();
5879                            term_clone.key_hash = synonym_term.1.0;
5880                            term_clone.key0 = synonym_term.1.1;
5881                            term_clone.term = synonym_term.0.clone();
5882
5883                            if let Some(existing) = unique_terms.get_mut(&synonym_term.0) {
5884                                existing
5885                                    .field_positions_vec
5886                                    .iter_mut()
5887                                    .zip(term_clone.field_positions_vec.iter())
5888                                    .for_each(|(x1, x2)| {
5889                                        x1.extend_from_slice(x2);
5890                                        x1.sort_unstable();
5891                                    });
5892                            } else {
5893                                unique_terms.insert(synonym_term.0.clone(), term_clone);
5894                            };
5895                        }
5896                    }
5897                }
5898            }
5899        }
5900
5901        for term in unique_terms {
5902            shard_mut.index_posting(term.1, docid_local, false, 0, 0, 0);
5903        }
5904
5905        match file {
5906            FileType::Path(file_path) => {
5907                if let Err(e) = shard_mut.copy_file(&file_path, docid_local) {
5908                    println!("can't copy PDF {} {}", file_path.display(), e);
5909                }
5910            }
5911
5912            FileType::Bytes(file_path, file_bytes) => {
5913                if let Err(e) = shard_mut.write_file(&file_bytes, docid_local) {
5914                    println!("can't copy PDF {} {}", file_path.display(), e);
5915                }
5916            }
5917
5918            _ => {}
5919        }
5920
5921        if !shard_mut.stored_field_names.is_empty() {
5922            shard_mut.store_document(docid_local, document_item.document);
5923        }
5924
5925        if do_commit {
5926            drop(shard_mut);
5927            warmup(self).await;
5928        }
5929    }
5930}
5931
5932pub(crate) struct DocumentItem {
5933    pub document: Document,
5934    pub unique_terms: AHashMap<String, TermObject>,
5935    pub field_vec: Vec<(usize, u8, u32, u32)>,
5936}