Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions plugins/tantivy/js/bindings.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,11 @@ async removeDocument(id: string, collection: string | null) : Promise<Result<nul

export type CreatedAtFilter = { gte: number | null; lte: number | null; gt: number | null; lt: number | null; eq: number | null }
export type SearchDocument = { id: string; doc_type: string; language: string | null; title: string; content: string; created_at: number }
export type SearchFilters = { created_at: CreatedAtFilter | null }
export type SearchFilters = { created_at: CreatedAtFilter | null; doc_type: string | null }
export type SearchHit = { score: number; document: SearchDocument }
export type SearchOptions = { fuzzy: boolean | null; distance: number | null }
export type SearchRequest = { query: string; collection?: string | null; filters?: SearchFilters; limit?: number; options?: SearchOptions }
export type SearchResult = { hits: SearchHit[] }
export type SearchResult = { hits: SearchHit[]; count: number }

/** tauri-specta globals **/

Expand Down
50 changes: 42 additions & 8 deletions plugins/tantivy/src/ext.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use tantivy::collector::TopDocs;
use tantivy::query::{BooleanQuery, FuzzyTermQuery, Occur, Query, QueryParser};
use tantivy::collector::{Count, TopDocs};
use tantivy::query::{
BooleanQuery, BoostQuery, FuzzyTermQuery, Occur, Query, QueryParser, TermQuery,
};
use tantivy::schema::IndexRecordOption;
use tantivy::{Index, ReloadPolicy, TantivyDocument, Term};
use tauri_plugin_path2::Path2PluginExt;

Expand Down Expand Up @@ -93,11 +96,16 @@ impl<'a, R: tauri::Runtime, M: tauri::Manager<R>> Tantivy<'a, R, M> {

let use_fuzzy = request.options.fuzzy.unwrap_or(false);

// Title boost factor (3x) to match Orama's title:3, content:1 behavior
const TITLE_BOOST: f32 = 3.0;

let mut combined_query: Box<dyn Query> = if use_fuzzy {
let distance = request.options.distance.unwrap_or(1);
let terms: Vec<&str> = request.query.split_whitespace().collect();
let mut subqueries: Vec<(Occur, Box<dyn Query>)> = Vec::new();
let mut term_queries: Vec<(Occur, Box<dyn Query>)> = Vec::new();

// For each term, create a Must clause that requires the term to match
// in either title OR content (with title boosted)
for term in terms {
let title_fuzzy =
FuzzyTermQuery::new(Term::from_field_text(fields.title, term), distance, true);
Expand All @@ -107,16 +115,28 @@ impl<'a, R: tauri::Runtime, M: tauri::Manager<R>> Tantivy<'a, R, M> {
true,
);

subqueries.push((Occur::Should, Box::new(title_fuzzy)));
subqueries.push((Occur::Should, Box::new(content_fuzzy)));
// Boost title matches by 3x
let boosted_title: Box<dyn Query> =
Box::new(BoostQuery::new(Box::new(title_fuzzy), TITLE_BOOST));
let content_query: Box<dyn Query> = Box::new(content_fuzzy);

// Each term must match in at least one field (title OR content)
let term_field_query = BooleanQuery::new(vec![
(Occur::Should, boosted_title),
(Occur::Should, content_query),
]);

// All terms must be present (Must for each term)
term_queries.push((Occur::Must, Box::new(term_field_query)));
}

Box::new(BooleanQuery::new(subqueries))
Box::new(BooleanQuery::new(term_queries))
} else {
let query_parser = QueryParser::for_index(index, vec![fields.title, fields.content]);
query_parser.parse_query(&request.query)?
};

// Apply created_at filter
if let Some(ref created_at_filter) = request.filters.created_at {
let range_query = build_created_at_range_query(fields.created_at, created_at_filter);
if let Some(rq) = range_query {
Expand All @@ -127,7 +147,21 @@ impl<'a, R: tauri::Runtime, M: tauri::Manager<R>> Tantivy<'a, R, M> {
}
}

let top_docs = searcher.search(&combined_query, &TopDocs::with_limit(request.limit))?;
// Apply doc_type filter
if let Some(ref doc_type) = request.filters.doc_type {
let doc_type_term = Term::from_field_text(fields.doc_type, doc_type);
let doc_type_query = TermQuery::new(doc_type_term, IndexRecordOption::Basic);
combined_query = Box::new(BooleanQuery::new(vec![
(Occur::Must, combined_query),
(Occur::Must, Box::new(doc_type_query)),
]));
}

// Use tuple collector to get both top docs and total count
let (top_docs, count) = searcher.search(
&combined_query,
&(TopDocs::with_limit(request.limit), Count),
)?;

let mut hits = Vec::new();
for (score, doc_address) in top_docs {
Expand All @@ -141,7 +175,7 @@ impl<'a, R: tauri::Runtime, M: tauri::Manager<R>> Tantivy<'a, R, M> {
}
}

Ok(SearchResult { hits })
Ok(SearchResult { hits, count })
}

pub async fn reindex(&self, collection: Option<String>) -> Result<(), crate::Error> {
Expand Down
2 changes: 2 additions & 0 deletions plugins/tantivy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pub struct SearchHit {
#[derive(Debug, Clone, Serialize, Deserialize, specta::Type)]
pub struct SearchResult {
pub hits: Vec<SearchHit>,
pub count: usize,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize, specta::Type)]
Expand All @@ -52,6 +53,7 @@ pub struct CreatedAtFilter {
#[derive(Debug, Clone, Default, Serialize, Deserialize, specta::Type)]
pub struct SearchFilters {
pub created_at: Option<CreatedAtFilter>,
pub doc_type: Option<String>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize, specta::Type)]
Expand Down
Loading