1use crate::common::types::ScoreType;
2#[cfg(feature = "api")]
3use itertools::Itertools as _;
4#[cfg(feature = "api")]
5use crate::segment::data_types::vectors::NamedQuery;
6use crate::segment::types::{Filter, SearchParams, WithPayloadInterface, WithVector};
7#[cfg(feature = "api")]
8use crate::segment::{data_types::vectors::VectorInternal, vector_storage::query::ContextPair};
9
10use crate::shard::query::query_enum::QueryEnum;
11
12#[derive(Clone, Debug, PartialEq)]
14pub struct CoreSearchRequest {
15 pub query: QueryEnum,
17 pub filter: Option<Filter>,
19 pub params: Option<SearchParams>,
21 pub limit: usize,
23 pub offset: usize,
27 pub with_payload: Option<WithPayloadInterface>,
29 pub with_vector: Option<WithVector>,
31 pub score_threshold: Option<ScoreType>,
32}
33
34impl CoreSearchRequest {
35 pub fn search_rate_cost(&self) -> usize {
36 let mut cost = self.query.search_cost();
37
38 if let Some(filter) = &self.filter {
39 cost += filter.total_conditions_count();
40 }
41
42 cost
43 }
44}
45
46#[cfg(feature = "api")]
47impl From<api::rest::SearchRequestInternal> for CoreSearchRequest {
48 fn from(request: api::rest::SearchRequestInternal) -> Self {
49 #[cfg(feature = "api")]
50 use crate::segment::data_types::vectors::NamedVectorStruct;
51
52 let api::rest::SearchRequestInternal {
53 vector,
54 filter,
55 score_threshold,
56 limit,
57 offset,
58 params,
59 with_vector,
60 with_payload,
61 } = request;
62 Self {
63 query: QueryEnum::Nearest(NamedQuery::from(NamedVectorStruct::from(vector))),
64 filter,
65 params,
66 limit,
67 offset: offset.unwrap_or_default(),
68 with_payload,
69 with_vector,
70 score_threshold,
71 }
72 }
73}
74
75#[cfg(feature = "api")]
76impl TryFrom<api::grpc::qdrant::CoreSearchPoints> for CoreSearchRequest {
77 type Error = tonic::Status;
78
79 fn try_from(value: api::grpc::qdrant::CoreSearchPoints) -> Result<Self, Self::Error> {
80 use crate::segment::data_types::vectors::VectorInternal;
81 use crate::segment::vector_storage::query::{ContextQuery, DiscoverQuery, RecoQuery};
82
83 let query = value
84 .query
85 .and_then(|query| query.query)
86 .map(|query| {
87 Ok(match query {
88 api::grpc::qdrant::query_enum::Query::NearestNeighbors(vector) => {
89 let vector_internal = VectorInternal::try_from(vector)?;
90 QueryEnum::Nearest(NamedQuery::from(
91 api::grpc::conversions::into_named_vector_struct(
92 value.vector_name,
93 vector_internal,
94 )?,
95 ))
96 }
97 api::grpc::qdrant::query_enum::Query::RecommendBestScore(query) => {
98 QueryEnum::RecommendBestScore(NamedQuery {
99 query: RecoQuery::try_from(query)?,
100 using: value.vector_name,
101 })
102 }
103 api::grpc::qdrant::query_enum::Query::RecommendSumScores(query) => {
104 QueryEnum::RecommendSumScores(NamedQuery {
105 query: RecoQuery::try_from(query)?,
106 using: value.vector_name,
107 })
108 }
109 api::grpc::qdrant::query_enum::Query::Discover(query) => {
110 let Some(target) = query.target else {
111 return Err(tonic::Status::invalid_argument("Target is not specified"));
112 };
113
114 let pairs = query
115 .context
116 .into_iter()
117 .map(try_context_pair_from_grpc)
118 .try_collect()?;
119
120 QueryEnum::Discover(NamedQuery {
121 query: DiscoverQuery::new(target.try_into()?, pairs),
122 using: value.vector_name,
123 })
124 }
125 api::grpc::qdrant::query_enum::Query::Context(query) => {
126 let pairs = query
127 .context
128 .into_iter()
129 .map(try_context_pair_from_grpc)
130 .try_collect()?;
131
132 QueryEnum::Context(NamedQuery {
133 query: ContextQuery::new(pairs),
134 using: value.vector_name,
135 })
136 }
137 })
138 })
139 .transpose()?
140 .ok_or_else(|| tonic::Status::invalid_argument("Query is not specified"))?;
141
142 Ok(Self {
143 query,
144 filter: value.filter.map(|f| f.try_into()).transpose()?,
145 params: value.params.map(Into::into),
146 limit: value.limit as usize,
147 offset: value.offset.unwrap_or_default() as usize,
148 with_payload: value.with_payload.map(|wp| wp.try_into()).transpose()?,
149 with_vector: Some(value.with_vectors.map(Into::into).unwrap_or_default()),
150 score_threshold: value.score_threshold,
151 })
152 }
153}
154
155#[cfg(feature = "api")]
156fn try_context_pair_from_grpc(
157 pair: api::grpc::qdrant::ContextPair,
158) -> Result<ContextPair<VectorInternal>, tonic::Status> {
159 let api::grpc::qdrant::ContextPair { positive, negative } = pair;
160 match (positive, negative) {
161 (Some(positive), Some(negative)) => Ok(ContextPair {
162 positive: positive.try_into()?,
163 negative: negative.try_into()?,
164 }),
165 _ => Err(tonic::Status::invalid_argument(
166 "All context pairs must have both positive and negative parts",
167 )),
168 }
169}
170
171#[cfg(feature = "api")]
172impl TryFrom<api::grpc::qdrant::SearchPoints> for CoreSearchRequest {
173 type Error = tonic::Status;
174
175 fn try_from(value: api::grpc::qdrant::SearchPoints) -> Result<Self, Self::Error> {
176 use crate::sparse::common::sparse_vector::validate_sparse_vector_impl;
177
178 let api::grpc::qdrant::SearchPoints {
179 collection_name: _,
180 vector,
181 filter,
182 limit,
183 with_payload,
184 params,
185 score_threshold,
186 offset,
187 vector_name,
188 with_vectors,
189 read_consistency: _,
190 timeout: _,
191 shard_key_selector: _,
192 sparse_indices,
193 } = value;
194
195 if let Some(sparse_indices) = &sparse_indices {
196 let api::grpc::qdrant::SparseIndices { data } = sparse_indices;
197 validate_sparse_vector_impl(data, &vector).map_err(|e| {
198 tonic::Status::invalid_argument(format!(
199 "Sparse indices does not match sparse vector conditions: {e}"
200 ))
201 })?;
202 }
203
204 let vector_internal =
205 VectorInternal::from_vector_and_indices(vector, sparse_indices.map(|v| v.data));
206
207 let vector_struct =
208 api::grpc::conversions::into_named_vector_struct(vector_name, vector_internal)?;
209
210 Ok(Self {
211 query: QueryEnum::Nearest(NamedQuery::from(vector_struct)),
212 filter: filter.map(Filter::try_from).transpose()?,
213 params: params.map(SearchParams::from),
214 limit: limit as usize,
215 offset: offset.map(|v| v as usize).unwrap_or_default(),
216 with_payload: with_payload
217 .map(WithPayloadInterface::try_from)
218 .transpose()?,
219 with_vector: with_vectors.map(WithVector::from),
220 score_threshold: score_threshold.map(|s| s as ScoreType),
221 })
222 }
223}
224
225#[derive(Debug, Clone)]
226pub struct CoreSearchRequestBatch {
227 pub searches: Vec<CoreSearchRequest>,
228}