From 1ceab57f3bb27e36c690882c21eba4d1401e1432 Mon Sep 17 00:00:00 2001 From: Priec Date: Wed, 29 Apr 2026 00:40:36 +0200 Subject: [PATCH] exact search endpoint --- common/proto/search.proto | 1 + common/src/proto/descriptor.bin | Bin 56961 -> 57100 bytes common/src/proto/komp_ac.search.rs | 68 +++++++++++++++++++++++++++++ search/src/lib.rs | 58 ++++++++++++++++++++++-- server | 2 +- 5 files changed, 124 insertions(+), 5 deletions(-) diff --git a/common/proto/search.proto b/common/proto/search.proto index 5069110..4d288db 100644 --- a/common/proto/search.proto +++ b/common/proto/search.proto @@ -4,6 +4,7 @@ package komp_ac.search; service Searcher { rpc SearchTable(SearchRequest) returns (SearchResponse); + rpc ExactSearchTable(SearchRequest) returns (SearchResponse); } message SearchRequest { diff --git a/common/src/proto/descriptor.bin b/common/src/proto/descriptor.bin index 4a8dc34245fb8874b53be56c039a93a536f65695..32330678c88e9dc2a5c0e11da2224d048e56a220 100644 GIT binary patch delta 686 zcmYk3J5Iwu6h%FsIPn|DpK+Xo5aJ{XH7h`ANJT}1Xb}wyK#0ylqNH=d3aBVRqC~I= zDwe~2{-*Qs^?Bnv@9|qb|EQ0ng?y#YY;h^%n<(zHE6-j~w0pR|xhtNsbMM6^*J|O6 zi@$6u`o(m)%MEGQk-{ohZpo(}1Cu*$NP;Gth7{=Jl!&ButoEFtz_Zd09B+^k#%V`7 zR*_t>!l0R`p&rP@4dw92&KtyZ%9iyML`EicB;Xl2Xoxc}Xb75#ZLQ&deL1Tm3C(Xc z?&*7JlR-Q4rw3&jVRhh)t1^56!GJG7YoSmxlp|G2AY>s8X-zI?Ts10gSDQT29tG5| zUgDz&Ld!TQ3L|KhggDltHs)^ep3djT6m~iE*e0!xhE~tpm^<2BQ}+VCHIt6v+u333 z0~0-KV+B9i(-ri@q+QP6p{dpa!kjd$76`(W^z(iF G`}Ggdz$sJ! delta 594 zcmYk3J5Iwu5Qg_LHuh}dwbwR;B*Y01iGmZ*P|I~mr`>-#5WMZ!@e zCX$&o3F&kcm28+uDB*OhHay|L19{?}0+7jif)=}FPa0IMyC#TO<#lzv*d<+oL;+{6 zD^N}wHSZ#YzD7koK{j#O6ErhB#h{9$qp2v{p(U!O(J`usq{cdl%5~#_kR?3ja}Fs2 z`kie3x4GO|iaFsUZzx}7VGN8vS)0NOn;daU#SlvjnI7nVfRyc+Lr+~k=-4o$, + ) -> std::result::Result, tonic::Status> { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/komp_ac.search.Searcher/ExactSearchTable", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("komp_ac.search.Searcher", "ExactSearchTable")); + self.inner.unary(req, path, codec).await + } } } /// Generated server implementations. @@ -159,6 +180,10 @@ pub mod searcher_server { &self, request: tonic::Request, ) -> std::result::Result, tonic::Status>; + async fn exact_search_table( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; } #[derive(Debug)] pub struct SearcherServer { @@ -279,6 +304,49 @@ pub mod searcher_server { }; Box::pin(fut) } + "/komp_ac.search.Searcher/ExactSearchTable" => { + #[allow(non_camel_case_types)] + struct ExactSearchTableSvc(pub Arc); + impl tonic::server::UnaryService + for ExactSearchTableSvc { + type Response = super::SearchResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::exact_search_table(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = ExactSearchTableSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } _ => { Box::pin(async move { let mut response = http::Response::new( diff --git a/search/src/lib.rs b/search/src/lib.rs index 365ebbf..228cdc6 100644 --- a/search/src/lib.rs +++ b/search/src/lib.rs @@ -20,6 +20,12 @@ const INDEX_ROOT: &str = "./tantivy_indexes"; const DEFAULT_RESULT_LIMIT: usize = 5; const SEARCH_RESULT_LIMIT: usize = 100; +#[derive(Clone, Copy)] +enum SearchMode { + Fuzzy, + Exact, +} + pub struct SearcherService { pub pool: PgPool, } @@ -165,7 +171,11 @@ async fn resolve_search_targets( } // Query building -fn build_query(index: &Index, normalized_query: &str) -> Result, Status> { +fn build_query( + index: &Index, + normalized_query: &str, + mode: SearchMode, +) -> Result, Status> { let schema = index.schema(); let prefix_edge_field = schema .get_field("prefix_edge") @@ -182,6 +192,24 @@ fn build_query(index: &Index, normalized_query: &str) -> Result)> = Vec::new(); // Layer 1: prefix @@ -276,6 +304,7 @@ async fn search_target( pool: &PgPool, target: &SearchTarget, query_str: &str, + mode: SearchMode, ) -> Result, Status> { if !target.index_path.exists() { return Ok(vec![]); @@ -286,7 +315,7 @@ async fn search_target( register_slovak_tokenizers(&index) .map_err(|e| Status::internal(format!("Failed to register Slovak tokenizers: {}", e)))?; - let Some(master_query) = build_query(&index, &normalize_slovak_text(query_str))? else { + let Some(master_query) = build_query(&index, &normalize_slovak_text(query_str), mode)? else { return Ok(vec![]); }; @@ -360,6 +389,23 @@ impl Searcher for SearcherService { async fn search_table( &self, request: Request, + ) -> Result, Status> { + self.run_search(request, SearchMode::Fuzzy).await + } + + async fn exact_search_table( + &self, + request: Request, + ) -> Result, Status> { + self.run_search(request, SearchMode::Exact).await + } +} + +impl SearcherService { + async fn run_search( + &self, + request: Request, + mode: SearchMode, ) -> Result, Status> { let req = request.into_inner(); let profile_name = req.profile_name.trim(); @@ -404,7 +450,7 @@ impl Searcher for SearcherService { // Merge per-table hits let mut hits = Vec::new(); for target in &targets { - hits.extend(search_target(&self.pool, target, query).await?); + hits.extend(search_target(&self.pool, target, query, mode).await?); } hits.sort_by(|left, right| right.score.total_cmp(&left.score)); @@ -413,7 +459,11 @@ impl Searcher for SearcherService { } info!( - "Processed search for profile '{}' (table scope: {}). Returning {} hits.", + "Processed {} search for profile '{}' (table scope: {}). Returning {} hits.", + match mode { + SearchMode::Fuzzy => "fuzzy", + SearchMode::Exact => "exact", + }, profile_name, requested_table.unwrap_or("*"), hits.len() diff --git a/server b/server index df65bbf..b26adc0 160000 --- a/server +++ b/server @@ -1 +1 @@ -Subproject commit df65bbf8f3317128aa4cf162628b52fe257fe84d +Subproject commit b26adc0cb0afeb4379da320ccb3fd6d4d3a241a4