Skip to main content

seekstorm/
lib.rs

1#![crate_type = "lib"]
2#![crate_name = "seekstorm"]
3#![doc(html_logo_url = "http://seekstorm.com/assets/logo.svg")]
4#![doc(html_favicon_url = "http://seekstorm.com/favicon.ico")]
5
6//! # `seekstorm`
7//! SeekStorm is an open-source, sub-millisecond vector and lexical search library & multi-tenancy server written in Rust.
8//! The **SeekStorm library** can be embedded into your program, while the **SeekStorm server** is a standalone search server to be accessed via HTTP.
9//! ### Add required crates to your project
10//! ```text
11//! cargo add seekstorm
12//! cargo add tokio
13//! cargo add serde_json
14//! ```
15//! ### use an asynchronous Rust runtime
16//! ```no_run
17//! use std::error::Error;
18//! #[tokio::main]
19//! async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
20//! // your SeekStorm code here
21//!   Ok(())
22//! }
23//! ```
24//! ### create index
25//! ```no_run
26//! # tokio_test::block_on(async {
27//! use std::path::Path;
28//! use std::sync::{Arc, RwLock};
29//! use seekstorm::index::{IndexMetaObject, Clustering, LexicalSimilarity,TokenizerType,StopwordType,FrequentwordType,AccessType,StemmerType,NgramSet,DocumentCompression,create_index};
30//! use seekstorm::vector::Inference;
31//! use seekstorm::vector_similarity::VectorSimilarity;
32//! let index_path=Path::new("C:/index/");
33//! let schema_json = r#"
34//! [{"field":"title","field_type":"Text","store":false,"index_lexical":false},
35//! {"field":"body","field_type":"Text","store":true,"index_lexical":true},
36//! {"field":"url","field_type":"Text","store":false,"index_lexical":false}]"#;
37//! let schema=serde_json::from_str(schema_json).unwrap();
38//! let meta = IndexMetaObject {
39//! id: 0,
40//! name: "test_index".to_string(),
41//! lexical_similarity: LexicalSimilarity::Bm25f,
42//! tokenizer: TokenizerType::AsciiAlphabetic,
43//! stemmer: StemmerType::None,
44//! stop_words: StopwordType::None,
45//! frequent_words: FrequentwordType::English,
46//! ngram_indexing: NgramSet::NgramFF as u8,
47//! document_compression:  DocumentCompression::Snappy,
48//! access_type: AccessType::Mmap,
49//! spelling_correction: None,
50//! query_completion: None,
51//! clustering: Clustering::None,
52//! inference: Inference::None,
53//! };
54//! let segment_number_bits1=11;
55//! let serialize_schema=true;
56//! let index_arc=create_index(index_path,meta,&schema,&Vec::new(),segment_number_bits1,false,None).await.unwrap();
57//! # });
58//! ```
59//! ### open index (alternatively to create index)
60//! ```no_run
61//! # tokio_test::block_on(async {
62//! use seekstorm::index::open_index;
63//! use std::path::Path;
64//! let index_path=Path::new("C:/index/");
65//! let index_arc=open_index(index_path).await.unwrap();
66//! # });
67//! ```
68//! ### index document
69//! ```no_run
70//! # tokio_test::block_on(async {
71//! # use std::path::Path;
72//! # use seekstorm::index::open_index;
73//! # let index_path=Path::new("C:/index/");
74//! # let index_arc=open_index(index_path).await.unwrap();
75//! use seekstorm::index::IndexDocument;
76//! use seekstorm::index::FileType;
77//! let document_json = r#"
78//! {"title":"title1 test","body":"body1","url":"url1"}"#;
79//! let document=serde_json::from_str(document_json).unwrap();
80//! index_arc.index_document(document,FileType::None).await;
81//! # });
82//! ```
83//! ### index documents
84//! ```no_run
85//! # tokio_test::block_on(async {
86//! # use std::path::Path;
87//! # use seekstorm::index::open_index;
88//! # let index_path=Path::new("C:/index/");
89//! # let index_arc=open_index(index_path).await.unwrap();
90//! use seekstorm::index::IndexDocuments;
91//! let documents_json = r#"
92//! [{"title":"title1 test","body":"body1","url":"url1"},
93//! {"title":"title2","body":"body2 test","url":"url2"},
94//! {"title":"title3 test","body":"body3 test","url":"url3"}]"#;
95//! let documents_vec=serde_json::from_str(documents_json).unwrap();
96//! index_arc.index_documents(documents_vec).await;
97//! # });
98//! ```
99//! ### delete documents by document id
100//! ```no_run
101//! # tokio_test::block_on(async {
102//! # use std::path::Path;
103//! # use seekstorm::index::open_index;
104//! # let index_path=Path::new("C:/index/");
105//! # let index_arc=open_index(index_path).await.unwrap();
106//! use seekstorm::index::DeleteDocuments;
107//! let docid_vec=vec![1,2];
108//! index_arc.delete_documents(docid_vec).await;
109//! # });
110//! ```
111//! ### delete documents by query
112//! ```no_run
113//! # tokio_test::block_on(async {
114//! # use std::path::Path;
115//! # use seekstorm::index::open_index;
116//! # let index_path=Path::new("C:/index/");
117//! # let index_arc=open_index(index_path).await.unwrap();
118//! use seekstorm::search::QueryType;
119//! use seekstorm::index::DeleteDocumentsByQuery;
120//! let query="test".to_string();
121//! let offset=0;
122//! let length=10;
123//! let query_type=QueryType::Intersection;
124//! let include_uncommitted=false;
125//! let field_filter=Vec::new();
126//! let facet_filter=Vec::new();
127//! let result_sort=Vec::new();
128//! index_arc.delete_documents_by_query(query, query_type, offset, length, include_uncommitted,field_filter,facet_filter,result_sort).await;
129//! # });
130//! ```
131//! ### update documents
132//! ```no_run
133//! # tokio_test::block_on(async {
134//! # use std::path::Path;
135//! # use seekstorm::index::open_index;
136//! # let index_path=Path::new("C:/index/");
137//! # let index_arc=open_index(index_path).await.unwrap();
138//! use seekstorm::index::UpdateDocuments;
139//! use seekstorm::commit::Commit;
140//! let id_document_vec_json = r#"
141//! [[1,{"title":"title1 test","body":"body1","url":"url1"}],
142//! [2,{"title":"title3 test","body":"body3 test","url":"url3"}]]"#;
143//! let id_document_vec=serde_json::from_str(id_document_vec_json).unwrap();
144//! index_arc.update_documents(id_document_vec).await;
145//! // ### commit documents
146//! index_arc.commit().await;
147//! # });
148//! ```
149//! ### search index
150//! ```no_run
151//! # tokio_test::block_on(async {
152//! # use std::path::Path;
153//! # use seekstorm::index::open_index;
154//! # let index_path=Path::new("C:/index/");
155//! # let index_arc=open_index(index_path).await.unwrap();
156//! use seekstorm::search::{Search, SearchMode, QueryType, ResultType, QueryRewriting};
157//! let query="test".to_string();
158//! let query_vector=None;
159//! let search_mode=SearchMode::Lexical;
160//! let enable_empty_query=false;
161//! let offset=10;
162//! let length=10;
163//! let query_type=QueryType::Intersection;
164//! let result_type=ResultType::TopkCount;
165//! let include_uncommitted=false;
166//! let field_filter=Vec::new();
167//! let query_facets=Vec::new();
168//! let facet_filter=Vec::new();
169//! let result_sort=Vec::new();
170//! let result_object = index_arc.search(query, query_vector, query_type, search_mode, enable_empty_query, offset, length, result_type,include_uncommitted,field_filter,query_facets,facet_filter,result_sort,QueryRewriting::SearchOnly).await;
171//! // ### display results
172//! use seekstorm::highlighter::{Highlight, highlighter};
173//! use std::collections::HashSet;
174//! let highlights:Vec<Highlight>= vec![
175//! Highlight {
176//!     field: "body".to_string(),
177//!     name:String::new(),
178//!     fragment_number: 2,
179//!     fragment_size: 160,
180//!     highlight_markup: true,
181//!     ..Default::default()
182//! },
183//! ];    
184//! let highlighter=Some(highlighter(&index_arc,highlights, result_object.query_terms).await);
185//! let return_fields_filter= HashSet::new();
186//! let distance_fields=Vec::new();
187//! let index=index_arc.read().await;
188//! for result in result_object.results.iter() {
189//!   let doc=index.get_document(result.doc_id,false,&highlighter,&return_fields_filter,&distance_fields).await.unwrap();
190//!   println!("result {} rank {} body field {:?}" , result.doc_id,result.score, doc.get("body"));
191//! }
192//! println!("result counts {} {} {}",result_object.results.len(), result_object.result_count, result_object.result_count_total);
193//! # });
194//! ```
195//! ### get document
196//! ```no_run
197//! # tokio_test::block_on(async {
198//! # use std::path::Path;
199//! # use seekstorm::index::open_index;
200//! # let index_path=Path::new("C:/index/");
201//! # let index_arc=open_index(index_path).await.unwrap();
202//! use std::collections::HashSet;
203//! let doc_id=0;
204//! let highlighter=None;
205//! let return_fields_filter= HashSet::new();
206//! let distance_fields=Vec::new();
207//! let index=index_arc.read().await;
208//! let doc=index.get_document(doc_id,false,&highlighter,&return_fields_filter,&distance_fields).await.unwrap();
209//! # });
210//! ```
211//! ### index JSON file in JSON, Newline-delimited JSON and Concatenated JSON format
212//! ```no_run
213//! # tokio_test::block_on(async {
214//! # use seekstorm::index::open_index;
215//! # let index_path=Path::new("C:/index/");
216//! # let mut index_arc=open_index(index_path).await.unwrap();
217//! use seekstorm::ingest::IngestJson;
218//! use std::path::Path;
219//! let file_path=Path::new("wiki-articles.json");
220//! let _ =index_arc.ingest_json(file_path).await;
221//! # });
222//! ```
223//! ### index all PDF files in directory and sub-directories
224//! - converts pdf to text and indexes it
225//! - extracts title from metatag, or first line of text, or from filename
226//! - extracts creation date from metatag, or from file creation date (Unix timestamp: the number of seconds since 1 January 1970)
227//! - copies all ingested pdf files to "files" subdirectory in index
228//! - the following index schema is required (and automatically created by the console `ingest` command):
229//! ```no_run
230//! let schema_json = r#"
231//! [
232//!   {
233//!     "field": "title",
234//!     "store": true,
235//!     "index_lexical": true,
236//!     "field_type": "Text",
237//!     "boost": 10
238//!   },
239//!   {
240//!     "field": "body",
241//!     "store": true,
242//!     "index_lexical": true,
243//!     "field_type": "Text"
244//!   },
245//!   {
246//!     "field": "url",
247//!     "store": true,
248//!     "index_lexical": false,
249//!     "field_type": "Text"
250//!   },
251//!   {
252//!     "field": "date",
253//!     "store": true,
254//!     "index_lexical": false,
255//!     "field_type": "Timestamp",
256//!     "facet": true
257//!   }
258//! ]"#;
259//! ```
260//! ```no_run
261//! # tokio_test::block_on(async {
262//! # use seekstorm::index::open_index;
263//! # let index_path=Path::new("C:/index/");
264//! # let mut index_arc=open_index(index_path).await.unwrap();
265//! use std::path::Path;
266//! use seekstorm::ingest::IngestPdf;
267//! let file_path=Path::new("C:/Users/johndoe/Downloads");
268//! let _ =index_arc.ingest_pdf(file_path).await;
269//! # });
270//! ```
271//! ### index PDF file
272//! ```no_run
273//! # tokio_test::block_on(async {
274//! # use seekstorm::index::open_index;
275//! # let index_path=Path::new("C:/index/");
276//! # let mut index_arc=open_index(index_path).await.unwrap();
277//! use std::path::Path;
278//! use seekstorm::ingest::IndexPdfFile;
279//! let file_path=Path::new("C:/test.pdf");
280//! let _ =index_arc.index_pdf_file(file_path).await;
281//! # });
282//! ```
283//! ### index PDF file bytes
284//! ```no_run
285//! # tokio_test::block_on(async {
286//! # use seekstorm::index::open_index;
287//! # let index_path=Path::new("C:/index/");
288//! # let mut index_arc=open_index(index_path).await.unwrap();
289//! use std::path::Path;
290//! use std::fs;
291//! use chrono::Utc;
292//! use seekstorm::ingest::IndexPdfBytes;
293//! let file_date=Utc::now().timestamp();
294//! let file_path=Path::new("C:/test.pdf");
295//! let document = fs::read(file_path).unwrap();
296//! let _ =index_arc.index_pdf_bytes(file_path, file_date, &document).await;
297//! # });
298//! ```
299//! ### get PDF file bytes
300//! ```no_run
301//! # tokio_test::block_on(async {
302//! # use seekstorm::index::open_index;
303//! # use std::path::Path;
304//! # let index_path=Path::new("C:/index/");
305//! # let mut index_arc=open_index(index_path).await.unwrap();
306//! let doc_id=0;
307//! let _file=index_arc.read().await.get_file(doc_id).await.unwrap();
308//! # });
309//! ```
310//! ### clear index
311//! ```no_run
312//! # tokio_test::block_on(async {
313//! # use seekstorm::index::open_index;
314//! # use std::path::Path;
315//! # let index_path=Path::new("C:/index/");
316//! # let mut index_arc=open_index(index_path).await.unwrap();
317//! index_arc.write().await.clear_index().await;
318//! # });
319//! ```
320//! ### delete index
321//! ```no_run
322//! # tokio_test::block_on(async {
323//! # use seekstorm::index::open_index;
324//! # use std::path::Path;
325//! # let index_path=Path::new("C:/index/");
326//! # let mut index_arc=open_index(index_path).await.unwrap();
327//! index_arc.write().await.delete_index();
328//! # });
329//! ```
330//! ### close index
331//! ```no_run
332//! # tokio_test::block_on(async {
333//! # use seekstorm::index::open_index;
334//! # use std::path::Path;
335//! # let index_path=Path::new("C:/index/");
336//! # let mut index_arc=open_index(index_path).await.unwrap();
337//! use seekstorm::index::Close;
338//! index_arc.close().await;
339//! # });
340//! ```
341//! ### seekstorm library version string
342//! ```no_run
343//! use seekstorm::index::version;
344//! let version=version();
345//! println!("version {}",version);
346//! ```
347//! ----------------
348//! ### Faceted search - Quick start
349//! **Facets are defined in 3 different places:**
350//! 1. the facet fields are defined in schema at create_index,
351//! 2. the facet field values are set in index_document at index time,
352//! 3. the query_facets/facet_filter search parameters are specified at query time.
353//!    Facets are then returned in the search result object.
354//!
355//! A minimal working example of faceted indexing & search requires just 60 lines of code. But to puzzle it all together from the documentation alone might be tedious.
356//! This is why we provide a quick start example here:
357//! ### create index
358//! ```no_run
359//! # tokio_test::block_on(async {
360//! use std::path::Path;
361//! use seekstorm::index::{IndexMetaObject, Clustering, LexicalSimilarity,TokenizerType,StopwordType,FrequentwordType,AccessType,StemmerType,NgramSet,DocumentCompression,create_index};
362//! use seekstorm::vector::Inference;
363//! use seekstorm::vector_similarity::VectorSimilarity;
364//! let index_path=Path::new("C:/index/");
365//! let schema_json = r#"
366//! [{"field":"title","field_type":"Text","store":false,"index_lexical":false},
367//! {"field":"body","field_type":"Text","store":true,"index_lexical":true},
368//! {"field":"url","field_type":"Text","store":true,"index_lexical":false},
369//! {"field":"town","field_type":"String15","store":false,"index_lexical":false,"facet":true}]"#;
370//! let schema=serde_json::from_str(schema_json).unwrap();
371//! let meta = IndexMetaObject {
372//!     id: 0,
373//!     name: "test_index".to_string(),
374//!     lexical_similarity: LexicalSimilarity::Bm25f,
375//!     tokenizer: TokenizerType::AsciiAlphabetic,
376//!     stemmer: StemmerType::None,
377//!     stop_words: StopwordType::None,
378//!     frequent_words: FrequentwordType::English,
379//!     ngram_indexing: NgramSet::NgramFF as u8,
380//!     document_compression:  DocumentCompression::Snappy,
381//!     access_type: AccessType::Mmap,
382//!     spelling_correction: None,
383//!     query_completion: None,
384//!     clustering: Clustering::None,
385//!     inference: Inference::None,
386//! };
387//! let serialize_schema=true;
388//! let segment_number_bits1=11;
389//! let index_arc=create_index(index_path,meta,&schema,&Vec::new(),segment_number_bits1,false,None).await.unwrap();
390//! # });
391//! ```
392//! ### index documents
393//! ```no_run
394//! # tokio_test::block_on(async {
395//! # use std::path::Path;
396//! # use seekstorm::index::open_index;
397//! # let index_path=Path::new("C:/index/");
398//! # let index_arc=open_index(index_path).await.unwrap();
399//! use seekstorm::index::IndexDocuments;
400//! use seekstorm::commit::Commit;
401//! use seekstorm::search::{QueryType, ResultType, QueryFacet, FacetFilter};
402//! let documents_json = r#"
403//! [{"title":"title1 test","body":"body1","url":"url1","town":"Berlin"},
404//! {"title":"title2","body":"body2 test","url":"url2","town":"Warsaw"},
405//! {"title":"title3 test","body":"body3 test","url":"url3","town":"New York"}]"#;
406//! let documents_vec=serde_json::from_str(documents_json).unwrap();
407//! index_arc.index_documents(documents_vec).await;
408//! // ### commit documents
409//! index_arc.commit().await;
410//! # });
411//! ```
412//! ### search index
413//! ```no_run
414//! # tokio_test::block_on(async {
415//! # use std::path::Path;
416//! # use seekstorm::index::open_index;
417//! # let index_path=Path::new("C:/index/");
418//! # let index_arc=open_index(index_path).await.unwrap();
419//! use seekstorm::search::{QueryType, SearchMode, ResultType, QueryFacet, FacetFilter, QueryRewriting,Search};
420//! let query="test".to_string();
421//! let query_vector=None;
422//! let search_mode=SearchMode::Lexical;
423//! let enable_empty_query=false;
424//! let offset=0;
425//! let length=10;
426//! let query_type=QueryType::Intersection;
427//! let result_type=ResultType::TopkCount;
428//! let include_uncommitted=false;
429//! let field_filter=Vec::new();
430//! let query_facets = vec![QueryFacet::String16 {field: "town".to_string(),prefix: "".to_string(),length: u16::MAX}];
431//! let facet_filter=Vec::new();
432//! //let facet_filter = vec![FacetFilter {field: "town".to_string(),   filter:Filter::String(vec!["Berlin".to_string()])}];
433//! let result_sort=Vec::new();
434//! let result_object = index_arc.search(query, query_vector, query_type, search_mode, enable_empty_query, offset, length, result_type,include_uncommitted,field_filter,query_facets,facet_filter,result_sort,QueryRewriting::SearchOnly).await;
435//! // ### display results
436//! use std::collections::HashSet;
437//! use seekstorm::highlighter::{highlighter, Highlight};
438//! let highlights:Vec<Highlight>= vec![
439//!         Highlight {
440//!             field: "body".to_owned(),
441//!             name:String::new(),
442//!             fragment_number: 2,
443//!             fragment_size: 160,
444//!             highlight_markup: true,
445//!            ..Default::default()
446//!         },
447//!     ];    
448//! let highlighter=Some(highlighter(&index_arc,highlights, result_object.query_terms).await);
449//! let return_fields_filter= HashSet::new();
450//! let distance_fields=Vec::new();
451//! let index=index_arc.write().await;
452//! for result in result_object.results.iter() {
453//!   let doc=index.get_document(result.doc_id,false,&highlighter,&return_fields_filter,&distance_fields).await.unwrap();
454//!   println!("result {} rank {} body field {:?}" , result.doc_id,result.score, doc.get("body"));
455//! }
456//! println!("result counts {} {} {}",result_object.results.len(), result_object.result_count, result_object.result_count_total);
457//! // ### display facets
458//! println!("{}", serde_json::to_string_pretty(&result_object.facets).unwrap());
459//! # });
460//! ```
461
462#![warn(
463    missing_docs,
464    unused_import_braces,
465    trivial_casts,
466    trivial_numeric_casts,
467    unused_qualifications
468)]
469
470/// include README.md in documentation
471#[cfg_attr(doctest, doc = include_str!("../../README.md"))]
472pub struct ReadmeDoctests;
473
474/// include FACETED_SEARCH.md in documentation
475#[cfg_attr(doctest, doc = include_str!("../../FACETED_SEARCH.md"))]
476pub struct ReadmeDoctests2;
477
478use std::sync::LazyLock;
479
480use tokio::runtime::Runtime;
481/// Global Tokio runtime for index operations, initialized lazily on first use.
482pub static INDEX_RUNTIME: LazyLock<Runtime> = LazyLock::new(|| {
483    tokio::runtime::Builder::new_multi_thread()
484        .worker_threads(num_cpus::get())
485        .thread_name("seekstorm-indexer")
486        .enable_all()
487        .build()
488        .unwrap()
489});
490
491pub(crate) mod add_result;
492pub(crate) mod clustering;
493/// Commit moves indexed documents from the intermediate uncompressed data structure in RAM
494/// to the final compressed data structure on disk.
495pub mod commit;
496pub(crate) mod compatible;
497pub(crate) mod compress_postinglist;
498pub(crate) mod doc_store;
499/// Geo search by indexing geo points (latitude, longitude), proximity searching for points within a specified radius, and proximity sorting.
500pub mod geo_search;
501/// Extracts the most relevant fragments (snippets, summaries) from specified fields of the document to provide a "keyword in context" (KWIC) functionality.
502/// With highlight_markup the matching query terms within the fragments can be highlighted with HTML markup.
503pub mod highlighter;
504/// Operate the index: reate_index, open_index, clear_index, close_index, delete_index, index_document(s)
505pub mod index;
506pub(crate) mod index_posting;
507/// Ingest JSON, Newline-delimited JSON, Concatenated JSON files, and PDF files into the index.
508pub mod ingest;
509pub(crate) mod intersection;
510pub(crate) mod intersection_simd;
511/// Iterator over all documents, also for search with empty query.
512pub mod iterator;
513pub(crate) mod min_heap;
514pub(crate) mod realtime_search;
515/// Search the index for all indexed documents, both for committed and uncommitted documents.
516/// The latter enables true realtime search: documents are available for search in exact the same millisecond they are indexed.
517pub mod search;
518pub(crate) mod single;
519/// Tokenizes text into tokens (words), supports Chinese word segmentation, folds (converts) diacritics, accents, zalgo text, umlaut, bold, italic, full-width UTF-8 characters into their basic representation.
520pub(crate) mod tokenizer;
521pub(crate) mod union;
522/// Utils `truncate()` and `substring()`
523pub mod utils;
524/// Vector search by indexing vectors and searching for similar vectors based on cosine similarity, inner product, and Euclidean distance.
525pub mod vector;
526/// Vector quantization and similarity measure definitions for vector search.
527pub mod vector_similarity;
528#[cfg(feature = "zh")]
529pub(crate) mod word_segmentation;