1use crate::error::{QdrantError, QdrantResult};
20use bytes::{BufMut, BytesMut};
21
22const SEARCH_COLLECTION: u8 = 0x0A;
29const SEARCH_VECTOR: u8 = 0x12;
31const SEARCH_FILTER: u8 = 0x1A;
33const SEARCH_LIMIT: u8 = 0x20;
35const SEARCH_WITH_PAYLOAD: u8 = 0x32;
37const SEARCH_SCORE_THRESHOLD: u8 = 0x45;
39const SEARCH_VECTOR_NAME: u8 = 0x52;
41const SEARCH_WITH_VECTORS: u8 = 0x5A;
43
44const SCROLL_COLLECTION: u8 = 0x0A;
50const SCROLL_FILTER: u8 = 0x12;
52const SCROLL_OFFSET: u8 = 0x1A;
54const SCROLL_LIMIT: u8 = 0x20;
56const SCROLL_WITH_PAYLOAD: u8 = 0x32;
58const SCROLL_WITH_VECTORS: u8 = 0x3A;
60
61const UPSERT_COLLECTION: u8 = 0x0A;
67const UPSERT_WAIT: u8 = 0x10;
69const UPSERT_POINTS: u8 = 0x1A;
71
72const POINT_ID: u8 = 0x0A;
78const POINT_VECTORS: u8 = 0x22;
80const POINT_PAYLOAD: u8 = 0x1A;
82
83const POINT_ID_NUM: u8 = 0x08;
89const POINT_ID_UUID: u8 = 0x12;
91
92const FILTER_SHOULD: u8 = 0x0A;
98const FILTER_MUST: u8 = 0x12;
100const FILTER_MUST_NOT: u8 = 0x1A;
102const CONDITION_FILTER: u8 = 0x22;
104const CONDITION_HAS_ID: u8 = 0x1A;
106const CONDITION_IS_NULL: u8 = 0x2A;
108
109#[inline]
116pub fn encode_varint(buf: &mut BytesMut, mut value: usize) {
117 loop {
118 let byte = (value & 0x7F) as u8;
119 value >>= 7;
120 if value == 0 {
121 buf.put_u8(byte);
122 break;
123 } else {
124 buf.put_u8(byte | 0x80);
125 }
126 }
127}
128
129#[inline]
131pub fn encode_varint_u64(buf: &mut BytesMut, mut value: u64) {
132 loop {
133 let byte = (value & 0x7F) as u8;
134 value >>= 7;
135 if value == 0 {
136 buf.put_u8(byte);
137 break;
138 } else {
139 buf.put_u8(byte | 0x80);
140 }
141 }
142}
143
144#[inline]
145fn extend_f32_le_slice(buf: &mut BytesMut, values: &[f32]) {
146 #[cfg(target_endian = "little")]
147 {
148 let float_bytes: &[u8] = bytemuck::cast_slice(values);
151 buf.extend_from_slice(float_bytes);
152 }
153
154 #[cfg(not(target_endian = "little"))]
155 {
156 for value in values {
157 buf.extend_from_slice(&value.to_le_bytes());
158 }
159 }
160}
161
162fn encode_error(message: impl Into<String>) -> QdrantError {
163 QdrantError::Encode(message.into())
164}
165
166fn ensure_non_empty_name(value: &str, label: &str) -> QdrantResult<()> {
167 if value.trim().is_empty() {
168 return Err(encode_error(format!("Qdrant {label} must not be empty")));
169 }
170 Ok(())
171}
172
173fn ensure_collection_name(collection: &str) -> QdrantResult<()> {
174 ensure_non_empty_name(collection, "collection name")
175}
176
177fn ensure_payload_key(key: &str) -> QdrantResult<()> {
178 ensure_non_empty_name(key, "payload field name")
179}
180
181fn ensure_vector_name(vector_name: Option<&str>) -> QdrantResult<()> {
182 if let Some(name) = vector_name {
183 ensure_non_empty_name(name, "vector name")?;
184 }
185 Ok(())
186}
187
188fn ensure_vector(label: &str, vector: &[f32]) -> QdrantResult<()> {
189 if vector.is_empty() {
190 return Err(encode_error(format!("Qdrant {label} must not be empty")));
191 }
192 if let Some((idx, value)) = vector
193 .iter()
194 .enumerate()
195 .find(|(_, value)| !value.is_finite())
196 {
197 return Err(encode_error(format!(
198 "Qdrant {label} contains non-finite vector value at index {idx}: {value}"
199 )));
200 }
201 Ok(())
202}
203
204fn ensure_search_limit(limit: u64) -> QdrantResult<()> {
205 if limit == 0 {
206 return Err(encode_error(
207 "Qdrant search limit must be greater than zero",
208 ));
209 }
210 Ok(())
211}
212
213fn ensure_scroll_limit(limit: u32) -> QdrantResult<()> {
214 if limit == 0 {
215 return Err(encode_error(
216 "Qdrant scroll limit must be greater than zero",
217 ));
218 }
219 Ok(())
220}
221
222fn ensure_score_threshold(score_threshold: Option<f32>) -> QdrantResult<()> {
223 if let Some(value) = score_threshold
224 && !value.is_finite()
225 {
226 return Err(encode_error(format!(
227 "Qdrant score threshold must be finite, got {value}"
228 )));
229 }
230 Ok(())
231}
232
233fn ensure_search_request(request: &SearchRequest<'_>) -> QdrantResult<()> {
234 ensure_collection_name(request.collection)?;
235 ensure_vector("search vector", request.vector)?;
236 ensure_search_limit(request.limit)?;
237 ensure_score_threshold(request.score_threshold)?;
238 ensure_vector_name(request.vector_name)
239}
240
241fn ensure_point_id(id: &crate::PointId, label: &str) -> QdrantResult<()> {
242 match id {
243 crate::PointId::Num(_) => Ok(()),
244 crate::PointId::Uuid(value) => ensure_non_empty_name(value, label),
245 }
246}
247
248fn ensure_point_ids(ids: &[crate::PointId], label: &str) -> QdrantResult<()> {
249 if ids.is_empty() {
250 return Err(encode_error(format!(
251 "Qdrant {label} point id list must not be empty"
252 )));
253 }
254 for id in ids {
255 ensure_point_id(id, label)?;
256 }
257 Ok(())
258}
259
260fn ensure_payload_value(value: &crate::point::PayloadValue, label: &str) -> QdrantResult<()> {
261 use crate::point::PayloadValue;
262
263 match value {
264 PayloadValue::Float(value) if !value.is_finite() => Err(encode_error(format!(
265 "Qdrant {label} contains non-finite payload float: {value}"
266 ))),
267 PayloadValue::List(items) => {
268 for item in items {
269 ensure_payload_value(item, label)?;
270 }
271 Ok(())
272 }
273 PayloadValue::Object(map) => ensure_payload(map, label),
274 _ => Ok(()),
275 }
276}
277
278fn ensure_payload(payload: &crate::point::Payload, label: &str) -> QdrantResult<()> {
279 for (key, value) in payload {
280 ensure_payload_key(key)?;
281 ensure_payload_value(value, label)?;
282 }
283 Ok(())
284}
285
286fn ensure_points(points: &[crate::Point]) -> QdrantResult<()> {
287 if points.is_empty() {
288 return Err(encode_error("Qdrant upsert point list must not be empty"));
289 }
290 for (idx, point) in points.iter().enumerate() {
291 ensure_point_id(&point.id, "upsert")?;
292 ensure_vector(&format!("upsert point {idx} vector"), &point.vector)?;
293 ensure_payload(&point.payload, &format!("upsert point {idx} payload"))?;
294 }
295 Ok(())
296}
297
298fn ensure_f64_finite(value: f64, label: &str) -> QdrantResult<()> {
299 if !value.is_finite() {
300 return Err(encode_error(format!(
301 "Qdrant {label} must be finite, got {value}"
302 )));
303 }
304 Ok(())
305}
306
307#[derive(Clone, Copy)]
313pub struct SearchRequest<'a> {
314 pub collection: &'a str,
315 pub vector: &'a [f32],
316 pub limit: u64,
317 pub score_threshold: Option<f32>,
318 pub vector_name: Option<&'a str>,
319 pub with_vectors: bool,
320}
321
322pub fn encode_search_proto(
335 buf: &mut BytesMut,
336 collection: &str,
337 vector: &[f32],
338 limit: u64,
339 score_threshold: Option<f32>,
340 vector_name: Option<&str>,
341 with_vectors: bool,
342) -> QdrantResult<()> {
343 ensure_search_request(&SearchRequest {
344 collection,
345 vector,
346 limit,
347 score_threshold,
348 vector_name,
349 with_vectors,
350 })?;
351
352 buf.clear();
353
354 buf.put_u8(SEARCH_COLLECTION);
356 encode_varint(buf, collection.len());
357 buf.extend_from_slice(collection.as_bytes());
358
359 buf.put_u8(SEARCH_VECTOR);
362 let vector_bytes_len = vector.len() * 4; encode_varint(buf, vector_bytes_len);
364
365 extend_f32_le_slice(buf, vector);
366
367 buf.put_u8(SEARCH_LIMIT);
369 encode_varint_u64(buf, limit);
370
371 encode_with_payload_true(buf);
373
374 if let Some(threshold) = score_threshold {
376 buf.put_u8(SEARCH_SCORE_THRESHOLD);
377 buf.put_f32_le(threshold);
378 }
379
380 if let Some(name) = vector_name {
382 buf.put_u8(SEARCH_VECTOR_NAME);
383 encode_varint(buf, name.len());
384 buf.extend_from_slice(name.as_bytes());
385 }
386
387 if with_vectors {
389 encode_search_with_vectors_selector(buf, vector_name);
390 }
391
392 Ok(())
393}
394
395pub fn encode_search_with_filter_proto(
400 buf: &mut BytesMut,
401 request: SearchRequest<'_>,
402 conditions: &[qail_core::ast::Condition],
403 is_or: bool,
404) -> QdrantResult<()> {
405 let (must_conditions, should_conditions): (
406 &[qail_core::ast::Condition],
407 &[qail_core::ast::Condition],
408 ) = if is_or {
409 (&[], conditions)
410 } else {
411 (conditions, &[])
412 };
413
414 encode_search_with_filter_groups_proto(buf, request, must_conditions, should_conditions)
415}
416
417pub fn encode_search_with_filter_groups_proto(
421 buf: &mut BytesMut,
422 request: SearchRequest<'_>,
423 must_conditions: &[qail_core::ast::Condition],
424 should_conditions: &[qail_core::ast::Condition],
425) -> QdrantResult<()> {
426 ensure_search_request(&request)?;
427 buf.clear();
428
429 buf.put_u8(SEARCH_COLLECTION);
431 encode_varint(buf, request.collection.len());
432 buf.extend_from_slice(request.collection.as_bytes());
433
434 buf.put_u8(SEARCH_VECTOR);
436 let vector_bytes_len = request.vector.len() * 4;
437 encode_varint(buf, vector_bytes_len);
438 extend_f32_le_slice(buf, request.vector);
439
440 if !must_conditions.is_empty() || !should_conditions.is_empty() {
442 let filter_buf = encode_filter_message_grouped(must_conditions, should_conditions)?;
443 buf.put_u8(SEARCH_FILTER);
444 encode_varint(buf, filter_buf.len());
445 buf.extend_from_slice(&filter_buf);
446 }
447
448 buf.put_u8(SEARCH_LIMIT);
450 encode_varint_u64(buf, request.limit);
451
452 encode_with_payload_true(buf);
454
455 if let Some(threshold) = request.score_threshold {
457 buf.put_u8(SEARCH_SCORE_THRESHOLD);
458 buf.put_f32_le(threshold);
459 }
460
461 if let Some(name) = request.vector_name {
463 buf.put_u8(SEARCH_VECTOR_NAME);
464 encode_varint(buf, name.len());
465 buf.extend_from_slice(name.as_bytes());
466 }
467
468 if request.with_vectors {
470 encode_search_with_vectors_selector(buf, request.vector_name);
471 }
472
473 Ok(())
474}
475
476pub fn encode_search_with_filter_grouped_cages_proto(
482 buf: &mut BytesMut,
483 request: SearchRequest<'_>,
484 must_conditions: &[qail_core::ast::Condition],
485 should_groups: &[Vec<qail_core::ast::Condition>],
486) -> QdrantResult<()> {
487 ensure_search_request(&request)?;
488 buf.clear();
489
490 buf.put_u8(SEARCH_COLLECTION);
492 encode_varint(buf, request.collection.len());
493 buf.extend_from_slice(request.collection.as_bytes());
494
495 buf.put_u8(SEARCH_VECTOR);
497 let vector_bytes_len = request.vector.len() * 4;
498 encode_varint(buf, vector_bytes_len);
499 extend_f32_le_slice(buf, request.vector);
500
501 if !must_conditions.is_empty() || !should_groups.is_empty() {
503 let filter_buf = encode_filter_message_grouped_cages(must_conditions, should_groups)?;
504 buf.put_u8(SEARCH_FILTER);
505 encode_varint(buf, filter_buf.len());
506 buf.extend_from_slice(&filter_buf);
507 }
508
509 buf.put_u8(SEARCH_LIMIT);
511 encode_varint_u64(buf, request.limit);
512
513 encode_with_payload_true(buf);
515
516 if let Some(threshold) = request.score_threshold {
518 buf.put_u8(SEARCH_SCORE_THRESHOLD);
519 buf.put_f32_le(threshold);
520 }
521
522 if let Some(name) = request.vector_name {
524 buf.put_u8(SEARCH_VECTOR_NAME);
525 encode_varint(buf, name.len());
526 buf.extend_from_slice(name.as_bytes());
527 }
528
529 if request.with_vectors {
531 encode_search_with_vectors_selector(buf, request.vector_name);
532 }
533
534 Ok(())
535}
536
537pub fn encode_with_payload_true(buf: &mut BytesMut) {
539 buf.put_u8(SEARCH_WITH_PAYLOAD);
542 encode_varint(buf, 2); buf.put_u8(0x08); buf.put_u8(0x01); }
546
547pub fn encode_search_with_vectors_true(buf: &mut BytesMut) {
549 buf.put_u8(SEARCH_WITH_VECTORS);
552 encode_varint(buf, 2); buf.put_u8(0x08); buf.put_u8(0x01); }
556
557pub fn encode_search_with_vectors_selector(buf: &mut BytesMut, vector_name: Option<&str>) {
562 let Some(name) = vector_name else {
563 encode_search_with_vectors_true(buf);
564 return;
565 };
566
567 let selector_len = 1 + varint_len(name.len() as u64) + name.len();
568 let include_len = 1 + varint_len(selector_len as u64) + selector_len;
569
570 buf.put_u8(SEARCH_WITH_VECTORS);
571 encode_varint(buf, include_len);
572 buf.put_u8(0x12); encode_varint(buf, selector_len);
574 buf.put_u8(0x0A); encode_varint(buf, name.len());
576 buf.extend_from_slice(name.as_bytes());
577}
578
579fn encode_filter_message_grouped(
607 must_conditions: &[qail_core::ast::Condition],
608 should_conditions: &[qail_core::ast::Condition],
609) -> QdrantResult<BytesMut> {
610 let mut filter_buf =
611 BytesMut::with_capacity((must_conditions.len() + should_conditions.len()) * 32);
612
613 let mut encode_clause =
614 |conditions: &[qail_core::ast::Condition], clause_tag: u8| -> QdrantResult<()> {
615 for cond in conditions {
616 let cond_buf = encode_condition_message(cond)?;
617
618 filter_buf.put_u8(clause_tag);
620 encode_varint(&mut filter_buf, cond_buf.len());
621 filter_buf.extend_from_slice(&cond_buf);
622 }
623 Ok(())
624 };
625
626 encode_clause(must_conditions, FILTER_MUST)?;
627 encode_clause(should_conditions, FILTER_SHOULD)?;
628
629 Ok(filter_buf)
630}
631
632fn encode_filter_message_grouped_cages(
634 must_conditions: &[qail_core::ast::Condition],
635 should_groups: &[Vec<qail_core::ast::Condition>],
636) -> QdrantResult<BytesMut> {
637 let grouped_condition_count: usize = should_groups.iter().map(Vec::len).sum();
638 let mut filter_buf =
639 BytesMut::with_capacity((must_conditions.len() + grouped_condition_count) * 32);
640
641 for cond in must_conditions {
642 let cond_buf = encode_condition_message(cond)?;
643 filter_buf.put_u8(FILTER_MUST);
644 encode_varint(&mut filter_buf, cond_buf.len());
645 filter_buf.extend_from_slice(&cond_buf);
646 }
647
648 for group in should_groups {
649 if group.is_empty() {
650 continue;
651 }
652
653 if group.len() == 1 {
654 let cond_buf = encode_condition_message(&group[0])?;
655 filter_buf.put_u8(FILTER_MUST);
656 encode_varint(&mut filter_buf, cond_buf.len());
657 filter_buf.extend_from_slice(&cond_buf);
658 continue;
659 }
660
661 let nested_filter = encode_filter_message_grouped(&[], group)?;
662 let mut nested_condition = BytesMut::with_capacity(nested_filter.len() + 4);
663 nested_condition.put_u8(CONDITION_FILTER);
664 encode_varint(&mut nested_condition, nested_filter.len());
665 nested_condition.extend_from_slice(&nested_filter);
666
667 filter_buf.put_u8(FILTER_MUST);
668 encode_varint(&mut filter_buf, nested_condition.len());
669 filter_buf.extend_from_slice(&nested_condition);
670 }
671
672 Ok(filter_buf)
673}
674
675fn encode_condition_message(cond: &qail_core::ast::Condition) -> QdrantResult<BytesMut> {
677 use qail_core::ast::{Expr, Operator, Value};
678
679 let key = match &cond.left {
680 Expr::Named(name) => name.as_str(),
681 Expr::Aliased { name, .. } => name.as_str(),
682 other => {
683 return Err(QdrantError::Encode(format!(
684 "Unsupported filter left expression for Qdrant: {:?}",
685 other
686 )));
687 }
688 };
689 let key = normalize_filter_key(key);
690 if key.is_empty() {
691 return Err(QdrantError::Encode(
692 "Qdrant filter key cannot be empty".to_string(),
693 ));
694 }
695
696 if key.eq_ignore_ascii_case("id") {
697 return match (&cond.op, &cond.value) {
698 (Operator::Eq, value) => encode_has_id_condition_from_value(value),
699 (Operator::In, value) => encode_has_id_condition_from_array(value),
700 (Operator::Ne, value) => {
701 encode_has_id_condition_from_value(value).map(encode_nested_must_not_condition)
702 }
703 (Operator::NotIn, value) => {
704 encode_has_id_condition_from_array(value).map(encode_nested_must_not_condition)
705 }
706 _ => Err(QdrantError::Encode(format!(
707 "Qdrant id filters support equality, inequality, IN, or NOT IN against integer, string, or UUID values: op={:?}, value={:?}",
708 cond.op, cond.value
709 ))),
710 };
711 }
712
713 match (&cond.op, &cond.value) {
714 (Operator::Eq, Value::String(s)) => Ok(encode_field_condition_match_keyword(key, s)),
716 (Operator::Eq, Value::Uuid(u)) => {
717 Ok(encode_field_condition_match_keyword(key, &u.to_string()))
718 }
719 (Operator::Eq, Value::Int(n)) => Ok(encode_field_condition_match_integer(key, *n)),
720 (Operator::Eq, Value::Bool(b)) => Ok(encode_field_condition_match_bool(key, *b)),
721 (Operator::In, Value::Array(values)) => encode_field_condition_match_any(key, values),
722 (Operator::Ne, Value::String(s)) => Ok(encode_nested_must_not_condition(
723 encode_field_condition_match_keyword(key, s),
724 )),
725 (Operator::Ne, Value::Uuid(u)) => Ok(encode_nested_must_not_condition(
726 encode_field_condition_match_keyword(key, &u.to_string()),
727 )),
728 (Operator::Ne, Value::Int(n)) => Ok(encode_nested_must_not_condition(
729 encode_field_condition_match_integer(key, *n),
730 )),
731 (Operator::Ne, Value::Bool(b)) => Ok(encode_nested_must_not_condition(
732 encode_field_condition_match_bool(key, *b),
733 )),
734 (Operator::NotIn, Value::Array(values)) => {
735 encode_field_condition_match_any(key, values).map(encode_nested_must_not_condition)
736 }
737
738 (Operator::Gt, Value::Int(n)) => Ok(encode_field_condition_range(
740 key,
741 None,
742 None,
743 Some(*n as f64),
744 None,
745 )),
746 (Operator::Gt, Value::Float(f)) => {
747 ensure_f64_finite(*f, "filter range float")?;
748 Ok(encode_field_condition_range(
749 key,
750 None,
751 None,
752 Some(*f),
753 None,
754 ))
755 }
756 (Operator::Gte, Value::Int(n)) => Ok(encode_field_condition_range(
757 key,
758 None,
759 None,
760 None,
761 Some(*n as f64),
762 )),
763 (Operator::Gte, Value::Float(f)) => {
764 ensure_f64_finite(*f, "filter range float")?;
765 Ok(encode_field_condition_range(
766 key,
767 None,
768 None,
769 None,
770 Some(*f),
771 ))
772 }
773 (Operator::Lt, Value::Int(n)) => Ok(encode_field_condition_range(
774 key,
775 Some(*n as f64),
776 None,
777 None,
778 None,
779 )),
780 (Operator::Lt, Value::Float(f)) => {
781 ensure_f64_finite(*f, "filter range float")?;
782 Ok(encode_field_condition_range(
783 key,
784 Some(*f),
785 None,
786 None,
787 None,
788 ))
789 }
790 (Operator::Lte, Value::Int(n)) => Ok(encode_field_condition_range(
791 key,
792 None,
793 Some(*n as f64),
794 None,
795 None,
796 )),
797 (Operator::Lte, Value::Float(f)) => {
798 ensure_f64_finite(*f, "filter range float")?;
799 Ok(encode_field_condition_range(
800 key,
801 None,
802 Some(*f),
803 None,
804 None,
805 ))
806 }
807
808 (Operator::Contains | Operator::Like, Value::String(s)) => {
810 if s.trim().is_empty() {
811 return Err(encode_error("Qdrant text filter value must not be empty"));
812 }
813 Ok(encode_field_condition_match_text(key, s))
814 }
815
816 (Operator::IsNull, Value::Null | Value::NullUuid) => Ok(encode_is_null_condition(key)),
817 (Operator::IsNotNull, Value::Null | Value::NullUuid) => Ok(
818 encode_nested_must_not_condition(encode_is_null_condition(key)),
819 ),
820 (Operator::NotLike, Value::String(s)) => {
821 if s.trim().is_empty() {
822 return Err(encode_error("Qdrant text filter value must not be empty"));
823 }
824 Ok(encode_nested_must_not_condition(
825 encode_field_condition_match_text(key, s),
826 ))
827 }
828
829 _ => Err(QdrantError::Encode(format!(
830 "Unsupported Qdrant filter condition: op={:?}, value={:?}",
831 cond.op, cond.value
832 ))),
833 }
834}
835
836fn normalize_filter_key(raw: &str) -> &str {
837 raw.trim().trim_matches('"').trim()
838}
839
840fn point_id_from_ast_value(value: &qail_core::ast::Value) -> Option<crate::PointId> {
841 use qail_core::ast::Value;
842
843 match value {
844 Value::Int(id) if *id >= 0 => Some(crate::PointId::Num(*id as u64)),
845 Value::String(id) => Some(crate::PointId::Uuid(id.clone())),
846 Value::Uuid(id) => Some(crate::PointId::Uuid(id.to_string())),
847 _ => None,
848 }
849}
850
851fn encode_point_id_message(id: &crate::PointId) -> BytesMut {
852 let mut id_buf = BytesMut::with_capacity(40);
853 match id {
854 crate::PointId::Num(n) => {
855 id_buf.put_u8(POINT_ID_NUM);
856 encode_varint_u64(&mut id_buf, *n);
857 }
858 crate::PointId::Uuid(s) => {
859 id_buf.put_u8(POINT_ID_UUID);
860 encode_varint(&mut id_buf, s.len());
861 id_buf.extend_from_slice(s.as_bytes());
862 }
863 }
864 id_buf
865}
866
867fn encode_has_id_condition_from_value(value: &qail_core::ast::Value) -> QdrantResult<BytesMut> {
868 let id = point_id_from_ast_value(value).ok_or_else(|| {
869 QdrantError::Encode(
870 "Qdrant id filters support only integer, string, or UUID values".to_string(),
871 )
872 })?;
873 ensure_point_id(&id, "id filter")?;
874 Ok(encode_has_id_condition(&id))
875}
876
877fn encode_has_id_condition_from_array(value: &qail_core::ast::Value) -> QdrantResult<BytesMut> {
878 let qail_core::ast::Value::Array(values) = value else {
879 return Err(QdrantError::Encode(
880 "Qdrant id IN filters require an array value".to_string(),
881 ));
882 };
883 if values.is_empty() {
884 return Err(QdrantError::Encode(
885 "Qdrant id IN filters require at least one id".to_string(),
886 ));
887 }
888
889 let ids = values
890 .iter()
891 .map(|value| {
892 let id = point_id_from_ast_value(value).ok_or_else(|| {
893 QdrantError::Encode(
894 "Qdrant id IN filters support only integer, string, or UUID values".to_string(),
895 )
896 })?;
897 ensure_point_id(&id, "id IN filter")?;
898 Ok(id)
899 })
900 .collect::<QdrantResult<Vec<_>>>()?;
901 Ok(encode_has_id_conditions(&ids))
902}
903
904fn encode_has_id_condition(id: &crate::PointId) -> BytesMut {
905 encode_has_id_conditions(std::slice::from_ref(id))
906}
907
908fn encode_has_id_conditions(ids: &[crate::PointId]) -> BytesMut {
909 let ids_len: usize = ids
910 .iter()
911 .map(|id| encode_point_id_message(id).len() + 2)
912 .sum();
913 let mut has_id_buf = BytesMut::with_capacity(ids_len);
914 for id in ids {
915 let id_buf = encode_point_id_message(id);
916 has_id_buf.put_u8(POINT_ID);
917 encode_varint(&mut has_id_buf, id_buf.len());
918 has_id_buf.extend_from_slice(&id_buf);
919 }
920
921 let mut cond_buf = BytesMut::with_capacity(has_id_buf.len() + 4);
922 cond_buf.put_u8(CONDITION_HAS_ID);
923 encode_varint(&mut cond_buf, has_id_buf.len());
924 cond_buf.extend_from_slice(&has_id_buf);
925 cond_buf
926}
927
928fn encode_nested_must_not_condition(inner: BytesMut) -> BytesMut {
929 let mut filter_buf = BytesMut::with_capacity(inner.len() + 8);
930 filter_buf.put_u8(FILTER_MUST_NOT);
931 encode_varint(&mut filter_buf, inner.len());
932 filter_buf.extend_from_slice(&inner);
933
934 let mut cond_buf = BytesMut::with_capacity(filter_buf.len() + 8);
935 cond_buf.put_u8(CONDITION_FILTER);
936 encode_varint(&mut cond_buf, filter_buf.len());
937 cond_buf.extend_from_slice(&filter_buf);
938 cond_buf
939}
940
941fn encode_is_null_condition(key: &str) -> BytesMut {
943 let mut is_null_buf = BytesMut::with_capacity(key.len() + 8);
944 is_null_buf.put_u8(0x0A); encode_varint(&mut is_null_buf, key.len());
946 is_null_buf.extend_from_slice(key.as_bytes());
947
948 let mut cond_buf = BytesMut::with_capacity(is_null_buf.len() + 8);
949 cond_buf.put_u8(CONDITION_IS_NULL);
950 encode_varint(&mut cond_buf, is_null_buf.len());
951 cond_buf.extend_from_slice(&is_null_buf);
952 cond_buf
953}
954
955fn encode_field_condition_match_keyword(key: &str, value: &str) -> BytesMut {
966 let mut match_buf = BytesMut::with_capacity(value.len() + 8);
968 match_buf.put_u8(0x0A); encode_varint(&mut match_buf, value.len());
970 match_buf.extend_from_slice(value.as_bytes());
971
972 let mut fc_buf = BytesMut::with_capacity(key.len() + match_buf.len() + 16);
974 fc_buf.put_u8(0x0A);
976 encode_varint(&mut fc_buf, key.len());
977 fc_buf.extend_from_slice(key.as_bytes());
978 fc_buf.put_u8(0x12);
980 encode_varint(&mut fc_buf, match_buf.len());
981 fc_buf.extend_from_slice(&match_buf);
982
983 let mut cond_buf = BytesMut::with_capacity(fc_buf.len() + 4);
985 cond_buf.put_u8(0x0A); encode_varint(&mut cond_buf, fc_buf.len());
987 cond_buf.extend_from_slice(&fc_buf);
988
989 cond_buf
990}
991
992fn encode_field_condition_match_integer(key: &str, value: i64) -> BytesMut {
994 let mut match_buf = BytesMut::with_capacity(16);
996 match_buf.put_u8(0x10); encode_varint_u64(&mut match_buf, value as u64);
998
999 let mut fc_buf = BytesMut::with_capacity(key.len() + match_buf.len() + 16);
1000 fc_buf.put_u8(0x0A);
1001 encode_varint(&mut fc_buf, key.len());
1002 fc_buf.extend_from_slice(key.as_bytes());
1003 fc_buf.put_u8(0x12);
1004 encode_varint(&mut fc_buf, match_buf.len());
1005 fc_buf.extend_from_slice(&match_buf);
1006
1007 let mut cond_buf = BytesMut::with_capacity(fc_buf.len() + 4);
1008 cond_buf.put_u8(0x0A);
1009 encode_varint(&mut cond_buf, fc_buf.len());
1010 cond_buf.extend_from_slice(&fc_buf);
1011
1012 cond_buf
1013}
1014
1015fn encode_field_condition_match_bool(key: &str, value: bool) -> BytesMut {
1017 let mut match_buf = BytesMut::with_capacity(4);
1019 match_buf.put_u8(0x18); match_buf.put_u8(if value { 1 } else { 0 });
1021
1022 let mut fc_buf = BytesMut::with_capacity(key.len() + match_buf.len() + 16);
1023 fc_buf.put_u8(0x0A);
1024 encode_varint(&mut fc_buf, key.len());
1025 fc_buf.extend_from_slice(key.as_bytes());
1026 fc_buf.put_u8(0x12);
1027 encode_varint(&mut fc_buf, match_buf.len());
1028 fc_buf.extend_from_slice(&match_buf);
1029
1030 let mut cond_buf = BytesMut::with_capacity(fc_buf.len() + 4);
1031 cond_buf.put_u8(0x0A);
1032 encode_varint(&mut cond_buf, fc_buf.len());
1033 cond_buf.extend_from_slice(&fc_buf);
1034
1035 cond_buf
1036}
1037
1038fn encode_field_condition_match_text(key: &str, value: &str) -> BytesMut {
1040 let mut match_buf = BytesMut::with_capacity(value.len() + 8);
1042 match_buf.put_u8(0x22); encode_varint(&mut match_buf, value.len());
1044 match_buf.extend_from_slice(value.as_bytes());
1045
1046 let mut fc_buf = BytesMut::with_capacity(key.len() + match_buf.len() + 16);
1047 fc_buf.put_u8(0x0A);
1048 encode_varint(&mut fc_buf, key.len());
1049 fc_buf.extend_from_slice(key.as_bytes());
1050 fc_buf.put_u8(0x12);
1051 encode_varint(&mut fc_buf, match_buf.len());
1052 fc_buf.extend_from_slice(&match_buf);
1053
1054 let mut cond_buf = BytesMut::with_capacity(fc_buf.len() + 4);
1055 cond_buf.put_u8(0x0A);
1056 encode_varint(&mut cond_buf, fc_buf.len());
1057 cond_buf.extend_from_slice(&fc_buf);
1058
1059 cond_buf
1060}
1061
1062fn encode_field_condition_match_any(
1063 key: &str,
1064 values: &[qail_core::ast::Value],
1065) -> QdrantResult<BytesMut> {
1066 use qail_core::ast::Value;
1067
1068 if values.is_empty() {
1069 return Err(encode_error("Qdrant IN filters require at least one value"));
1070 }
1071 if values
1072 .iter()
1073 .all(|value| matches!(value, Value::String(_) | Value::Uuid(_)))
1074 {
1075 let mut repeated = BytesMut::with_capacity(values.len() * 16);
1076 for value in values {
1077 let value = match value {
1078 Value::String(value) => value.clone(),
1079 Value::Uuid(value) => value.to_string(),
1080 _ => unreachable!("checked by all()"),
1081 };
1082 repeated.put_u8(0x0A); encode_varint(&mut repeated, value.len());
1084 repeated.extend_from_slice(value.as_bytes());
1085 }
1086
1087 let mut match_buf = BytesMut::with_capacity(repeated.len() + 4);
1088 match_buf.put_u8(0x2A); encode_varint(&mut match_buf, repeated.len());
1090 match_buf.extend_from_slice(&repeated);
1091 return Ok(encode_field_condition_match_message(key, match_buf));
1092 }
1093 if values.iter().all(|value| matches!(value, Value::Int(_))) {
1094 let mut repeated = BytesMut::with_capacity(values.len() * 10);
1095 for value in values {
1096 let Value::Int(value) = value else {
1097 unreachable!("checked by all()");
1098 };
1099 repeated.put_u8(0x08); encode_varint_u64(&mut repeated, *value as u64);
1101 }
1102
1103 let mut match_buf = BytesMut::with_capacity(repeated.len() + 4);
1104 match_buf.put_u8(0x32); encode_varint(&mut match_buf, repeated.len());
1106 match_buf.extend_from_slice(&repeated);
1107 return Ok(encode_field_condition_match_message(key, match_buf));
1108 }
1109
1110 Err(encode_error(
1111 "Qdrant IN filters support only a non-empty homogeneous string/UUID or integer array",
1112 ))
1113}
1114
1115fn encode_field_condition_match_message(key: &str, match_buf: BytesMut) -> BytesMut {
1116 let mut fc_buf = BytesMut::with_capacity(key.len() + match_buf.len() + 16);
1117 fc_buf.put_u8(0x0A);
1118 encode_varint(&mut fc_buf, key.len());
1119 fc_buf.extend_from_slice(key.as_bytes());
1120 fc_buf.put_u8(0x12);
1121 encode_varint(&mut fc_buf, match_buf.len());
1122 fc_buf.extend_from_slice(&match_buf);
1123
1124 let mut cond_buf = BytesMut::with_capacity(fc_buf.len() + 4);
1125 cond_buf.put_u8(0x0A);
1126 encode_varint(&mut cond_buf, fc_buf.len());
1127 cond_buf.extend_from_slice(&fc_buf);
1128
1129 cond_buf
1130}
1131
1132fn encode_field_condition_range(
1136 key: &str,
1137 lt: Option<f64>,
1138 lte: Option<f64>,
1139 gt: Option<f64>,
1140 gte: Option<f64>,
1141) -> BytesMut {
1142 let mut range_buf = BytesMut::with_capacity(40);
1148 if let Some(v) = lt {
1149 range_buf.put_u8(0x09); range_buf.put_f64_le(v);
1151 }
1152 if let Some(v) = gt {
1153 range_buf.put_u8(0x11); range_buf.put_f64_le(v);
1155 }
1156 if let Some(v) = gte {
1157 range_buf.put_u8(0x19); range_buf.put_f64_le(v);
1159 }
1160 if let Some(v) = lte {
1161 range_buf.put_u8(0x21); range_buf.put_f64_le(v);
1163 }
1164
1165 let mut fc_buf = BytesMut::with_capacity(key.len() + range_buf.len() + 16);
1167 fc_buf.put_u8(0x0A); encode_varint(&mut fc_buf, key.len());
1169 fc_buf.extend_from_slice(key.as_bytes());
1170 fc_buf.put_u8(0x1A); encode_varint(&mut fc_buf, range_buf.len());
1172 fc_buf.extend_from_slice(&range_buf);
1173
1174 let mut cond_buf = BytesMut::with_capacity(fc_buf.len() + 4);
1176 cond_buf.put_u8(0x0A);
1177 encode_varint(&mut cond_buf, fc_buf.len());
1178 cond_buf.extend_from_slice(&fc_buf);
1179
1180 cond_buf
1181}
1182
1183pub fn encode_upsert_proto(
1189 buf: &mut BytesMut,
1190 collection: &str,
1191 points: &[crate::Point],
1192 wait: bool,
1193) -> QdrantResult<()> {
1194 ensure_collection_name(collection)?;
1195 ensure_points(points)?;
1196
1197 buf.clear();
1198
1199 buf.put_u8(UPSERT_COLLECTION);
1201 encode_varint(buf, collection.len());
1202 buf.extend_from_slice(collection.as_bytes());
1203
1204 if wait {
1206 buf.put_u8(UPSERT_WAIT);
1207 buf.put_u8(0x01);
1208 }
1209
1210 for point in points {
1212 encode_point_struct(buf, point)?;
1213 }
1214
1215 Ok(())
1216}
1217
1218fn encode_point_struct(buf: &mut BytesMut, point: &crate::Point) -> QdrantResult<()> {
1220 let mut point_buf = BytesMut::with_capacity(point.vector.len() * 4 + 64);
1223
1224 encode_point_id_field(&mut point_buf, &point.id);
1226
1227 if !point.payload.is_empty() {
1229 encode_payload_map(&mut point_buf, &point.payload)?;
1230 }
1231
1232 let vector_bytes_len = point.vector.len() * 4;
1234 let vector_inner_len = 1 + varint_len(vector_bytes_len as u64) + vector_bytes_len;
1235 let vectors_len = 1 + varint_len(vector_inner_len as u64) + vector_inner_len;
1236
1237 point_buf.put_u8(POINT_VECTORS);
1238 encode_varint(&mut point_buf, vectors_len);
1239 point_buf.put_u8(0x0A); encode_varint(&mut point_buf, vector_inner_len);
1241 point_buf.put_u8(0x0A); encode_varint(&mut point_buf, vector_bytes_len);
1243 extend_f32_le_slice(&mut point_buf, &point.vector);
1244
1245 buf.put_u8(UPSERT_POINTS);
1247 encode_varint(buf, point_buf.len());
1248 buf.extend_from_slice(&point_buf);
1249 Ok(())
1250}
1251
1252fn encode_point_id_field(buf: &mut BytesMut, id: &crate::PointId) {
1254 let id_buf = encode_point_id_message(id);
1255 buf.put_u8(POINT_ID);
1256 encode_varint(buf, id_buf.len());
1257 buf.extend_from_slice(&id_buf);
1258}
1259
1260fn encode_payload_map(buf: &mut BytesMut, payload: &crate::point::Payload) -> QdrantResult<()> {
1264 for (key, value) in payload {
1265 ensure_payload_key(key)?;
1266 let mut entry_buf = BytesMut::with_capacity(key.len() + 32);
1267
1268 entry_buf.put_u8(0x0A);
1270 encode_varint(&mut entry_buf, key.len());
1271 entry_buf.extend_from_slice(key.as_bytes());
1272
1273 let value_buf = encode_payload_value(value)?;
1275 entry_buf.put_u8(0x12);
1276 encode_varint(&mut entry_buf, value_buf.len());
1277 entry_buf.extend_from_slice(&value_buf);
1278
1279 buf.put_u8(POINT_PAYLOAD);
1281 encode_varint(buf, entry_buf.len());
1282 buf.extend_from_slice(&entry_buf);
1283 }
1284 Ok(())
1285}
1286
1287fn encode_payload_value(value: &crate::point::PayloadValue) -> QdrantResult<BytesMut> {
1303 use crate::point::PayloadValue;
1304 let mut buf = BytesMut::with_capacity(32);
1305
1306 match value {
1307 PayloadValue::Null => {
1308 buf.put_u8(0x08);
1310 buf.put_u8(0x00);
1311 }
1312 PayloadValue::Float(f) => {
1313 ensure_f64_finite(*f, "payload float")?;
1314 buf.put_u8(0x11); buf.put_f64_le(*f);
1317 }
1318 PayloadValue::Integer(n) => {
1319 buf.put_u8(0x18); encode_varint_u64(&mut buf, *n as u64);
1322 }
1323 PayloadValue::String(s) => {
1324 buf.put_u8(0x22); encode_varint(&mut buf, s.len());
1327 buf.extend_from_slice(s.as_bytes());
1328 }
1329 PayloadValue::Bool(b) => {
1330 buf.put_u8(0x28); buf.put_u8(if *b { 1 } else { 0 });
1333 }
1334 PayloadValue::List(items) => {
1335 let mut list_buf = BytesMut::with_capacity(items.len() * 16);
1337 for item in items {
1338 let val_buf = encode_payload_value(item)?;
1339 list_buf.put_u8(0x0A);
1341 encode_varint(&mut list_buf, val_buf.len());
1342 list_buf.extend_from_slice(&val_buf);
1343 }
1344 buf.put_u8(0x3A); encode_varint(&mut buf, list_buf.len());
1346 buf.extend_from_slice(&list_buf);
1347 }
1348 PayloadValue::Object(map) => {
1349 let mut struct_buf = BytesMut::with_capacity(map.len() * 32);
1352 for (k, v) in map {
1353 ensure_payload_key(k)?;
1354 let val_buf = encode_payload_value(v)?;
1355 let mut entry_buf = BytesMut::with_capacity(k.len() + val_buf.len() + 8);
1356 entry_buf.put_u8(0x0A);
1358 encode_varint(&mut entry_buf, k.len());
1359 entry_buf.extend_from_slice(k.as_bytes());
1360 entry_buf.put_u8(0x12);
1362 encode_varint(&mut entry_buf, val_buf.len());
1363 entry_buf.extend_from_slice(&val_buf);
1364 struct_buf.put_u8(0x0A);
1366 encode_varint(&mut struct_buf, entry_buf.len());
1367 struct_buf.extend_from_slice(&entry_buf);
1368 }
1369 buf.put_u8(0x32); encode_varint(&mut buf, struct_buf.len());
1371 buf.extend_from_slice(&struct_buf);
1372 }
1373 }
1374
1375 Ok(buf)
1376}
1377
1378pub fn encode_get_points_proto(
1393 buf: &mut BytesMut,
1394 collection: &str,
1395 ids: &[crate::PointId],
1396 with_vectors: bool,
1397) -> QdrantResult<()> {
1398 ensure_collection_name(collection)?;
1399 ensure_point_ids(ids, "get")?;
1400
1401 buf.clear();
1402
1403 buf.put_u8(0x0A);
1405 encode_varint(buf, collection.len());
1406 buf.extend_from_slice(collection.as_bytes());
1407
1408 for id in ids {
1410 let id_buf = encode_point_id_message(id);
1411 buf.put_u8(0x12); encode_varint(buf, id_buf.len());
1413 buf.extend_from_slice(&id_buf);
1414 }
1415
1416 buf.put_u8(0x22); encode_varint(buf, 2);
1419 buf.put_u8(0x08); buf.put_u8(0x01);
1421
1422 if with_vectors {
1424 buf.put_u8(0x2A); encode_varint(buf, 2);
1426 buf.put_u8(0x08); buf.put_u8(0x01);
1428 }
1429 Ok(())
1430}
1431
1432pub fn encode_scroll_points_proto(
1449 buf: &mut BytesMut,
1450 collection: &str,
1451 limit: u32,
1452 offset: Option<&crate::PointId>,
1453 with_vectors: bool,
1454) -> QdrantResult<()> {
1455 ensure_collection_name(collection)?;
1456 ensure_scroll_limit(limit)?;
1457 if let Some(id) = offset {
1458 ensure_point_id(id, "scroll offset")?;
1459 }
1460
1461 buf.clear();
1462
1463 buf.put_u8(SCROLL_COLLECTION);
1465 encode_varint(buf, collection.len());
1466 buf.extend_from_slice(collection.as_bytes());
1467
1468 if let Some(id) = offset {
1470 let id_buf = encode_point_id_message(id);
1471 buf.put_u8(SCROLL_OFFSET);
1472 encode_varint(buf, id_buf.len());
1473 buf.extend_from_slice(&id_buf);
1474 }
1475
1476 buf.put_u8(SCROLL_LIMIT);
1478 encode_varint(buf, limit as usize);
1479
1480 buf.put_u8(SCROLL_WITH_PAYLOAD);
1482 encode_varint(buf, 2);
1483 buf.put_u8(0x08);
1484 buf.put_u8(0x01);
1485
1486 if with_vectors {
1488 buf.put_u8(SCROLL_WITH_VECTORS);
1489 encode_varint(buf, 2);
1490 buf.put_u8(0x08);
1491 buf.put_u8(0x01);
1492 }
1493 Ok(())
1494}
1495
1496pub fn encode_scroll_points_with_filter_grouped_cages_proto(
1498 buf: &mut BytesMut,
1499 collection: &str,
1500 limit: u32,
1501 offset: Option<&crate::PointId>,
1502 with_vectors: bool,
1503 must_conditions: &[qail_core::ast::Condition],
1504 should_groups: &[Vec<qail_core::ast::Condition>],
1505) -> QdrantResult<()> {
1506 ensure_collection_name(collection)?;
1507 ensure_scroll_limit(limit)?;
1508 if let Some(id) = offset {
1509 ensure_point_id(id, "scroll offset")?;
1510 }
1511
1512 buf.clear();
1513
1514 buf.put_u8(SCROLL_COLLECTION);
1516 encode_varint(buf, collection.len());
1517 buf.extend_from_slice(collection.as_bytes());
1518
1519 if !must_conditions.is_empty() || !should_groups.is_empty() {
1521 let filter_buf = encode_filter_message_grouped_cages(must_conditions, should_groups)?;
1522 buf.put_u8(SCROLL_FILTER);
1523 encode_varint(buf, filter_buf.len());
1524 buf.extend_from_slice(&filter_buf);
1525 }
1526
1527 if let Some(id) = offset {
1529 let id_buf = encode_point_id_message(id);
1530 buf.put_u8(SCROLL_OFFSET);
1531 encode_varint(buf, id_buf.len());
1532 buf.extend_from_slice(&id_buf);
1533 }
1534
1535 buf.put_u8(SCROLL_LIMIT);
1537 encode_varint(buf, limit as usize);
1538
1539 buf.put_u8(SCROLL_WITH_PAYLOAD);
1541 encode_varint(buf, 2);
1542 buf.put_u8(0x08);
1543 buf.put_u8(0x01);
1544
1545 if with_vectors {
1547 buf.put_u8(SCROLL_WITH_VECTORS);
1548 encode_varint(buf, 2);
1549 buf.put_u8(0x08);
1550 buf.put_u8(0x01);
1551 }
1552
1553 Ok(())
1554}
1555
1556pub fn encode_set_payload_proto(
1571 buf: &mut BytesMut,
1572 collection: &str,
1573 point_ids: &[crate::PointId],
1574 payload: &crate::point::Payload,
1575 wait: bool,
1576) -> QdrantResult<()> {
1577 ensure_collection_name(collection)?;
1578 ensure_point_ids(point_ids, "payload update")?;
1579 if payload.is_empty() {
1580 return Err(encode_error("Qdrant payload update must not be empty"));
1581 }
1582 ensure_payload(payload, "payload update")?;
1583
1584 buf.clear();
1585
1586 buf.put_u8(0x0A);
1588 encode_varint(buf, collection.len());
1589 buf.extend_from_slice(collection.as_bytes());
1590
1591 if wait {
1593 buf.put_u8(0x10);
1594 buf.put_u8(0x01);
1595 }
1596
1597 for (key, value) in payload {
1599 ensure_payload_key(key)?;
1600 let mut entry_buf = BytesMut::with_capacity(key.len() + 32);
1601 entry_buf.put_u8(0x0A); encode_varint(&mut entry_buf, key.len());
1603 entry_buf.extend_from_slice(key.as_bytes());
1604
1605 let val_buf = encode_payload_value(value)?;
1606 entry_buf.put_u8(0x12); encode_varint(&mut entry_buf, val_buf.len());
1608 entry_buf.extend_from_slice(&val_buf);
1609
1610 buf.put_u8(0x1A); encode_varint(buf, entry_buf.len());
1612 buf.extend_from_slice(&entry_buf);
1613 }
1614
1615 let selector_buf = encode_points_selector(point_ids);
1617 buf.put_u8(0x2A); encode_varint(buf, selector_buf.len());
1619 buf.extend_from_slice(&selector_buf);
1620 Ok(())
1621}
1622
1623pub fn encode_create_field_index_proto(
1640 buf: &mut BytesMut,
1641 collection: &str,
1642 field_name: &str,
1643 field_type: FieldType,
1644 wait: bool,
1645) -> QdrantResult<()> {
1646 ensure_collection_name(collection)?;
1647 ensure_payload_key(field_name)?;
1648
1649 buf.clear();
1650
1651 buf.put_u8(0x0A);
1653 encode_varint(buf, collection.len());
1654 buf.extend_from_slice(collection.as_bytes());
1655
1656 if wait {
1658 buf.put_u8(0x10);
1659 buf.put_u8(0x01);
1660 }
1661
1662 buf.put_u8(0x1A);
1664 encode_varint(buf, field_name.len());
1665 buf.extend_from_slice(field_name.as_bytes());
1666
1667 buf.put_u8(0x20); encode_varint(buf, field_type as usize);
1670 Ok(())
1671}
1672
1673#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1675#[repr(u8)]
1676pub enum FieldType {
1677 Keyword = 0,
1679 Integer = 1,
1681 Float = 2,
1683 Geo = 3,
1685 Text = 4,
1687 Bool = 5,
1689 Datetime = 6,
1691}
1692
1693const CREATE_COLLECTION_NAME: u8 = 0x0A;
1699const CREATE_VECTORS_CONFIG: u8 = 0x52;
1701const CREATE_ON_DISK: u8 = 0x40;
1703
1704const DELETE_COLLECTION_NAME: u8 = 0x0A;
1710
1711pub fn encode_create_collection_proto(
1713 buf: &mut BytesMut,
1714 collection_name: &str,
1715 vector_size: u64,
1716 distance: crate::Distance,
1717 on_disk: bool,
1718) -> QdrantResult<()> {
1719 ensure_collection_name(collection_name)?;
1720 if vector_size == 0 {
1721 return Err(encode_error(
1722 "Qdrant collection vector_size must be greater than zero",
1723 ));
1724 }
1725
1726 buf.clear();
1727
1728 buf.put_u8(CREATE_COLLECTION_NAME);
1730 encode_varint(buf, collection_name.len());
1731 buf.extend_from_slice(collection_name.as_bytes());
1732
1733 let mut params_buf = BytesMut::with_capacity(32);
1735
1736 params_buf.put_u8(0x08);
1738 encode_varint_u64(&mut params_buf, vector_size);
1739
1740 params_buf.put_u8(0x10);
1742 let distance_val = match distance {
1743 crate::Distance::Cosine => 1,
1744 crate::Distance::Euclidean => 2,
1745 crate::Distance::Dot => 3,
1746 };
1747 encode_varint(&mut params_buf, distance_val);
1748
1749 if on_disk {
1751 params_buf.put_u8(0x28);
1752 params_buf.put_u8(0x01);
1753 }
1754
1755 let mut config_buf = BytesMut::with_capacity(params_buf.len() + 4);
1757 config_buf.put_u8(0x0A);
1758 encode_varint(&mut config_buf, params_buf.len());
1759 config_buf.extend_from_slice(¶ms_buf);
1760
1761 buf.put_u8(CREATE_VECTORS_CONFIG);
1763 encode_varint(buf, config_buf.len());
1764 buf.extend_from_slice(&config_buf);
1765
1766 if on_disk {
1767 buf.put_u8(CREATE_ON_DISK);
1768 buf.put_u8(0x01);
1769 }
1770 Ok(())
1771}
1772
1773pub fn encode_delete_collection_proto(
1775 buf: &mut BytesMut,
1776 collection_name: &str,
1777) -> QdrantResult<()> {
1778 ensure_collection_name(collection_name)?;
1779 buf.clear();
1780 buf.put_u8(DELETE_COLLECTION_NAME);
1781 encode_varint(buf, collection_name.len());
1782 buf.extend_from_slice(collection_name.as_bytes());
1783 Ok(())
1784}
1785
1786pub fn encode_delete_points_mixed_proto(
1800 buf: &mut BytesMut,
1801 collection_name: &str,
1802 point_ids: &[crate::PointId],
1803) -> QdrantResult<()> {
1804 ensure_collection_name(collection_name)?;
1805 ensure_point_ids(point_ids, "delete")?;
1806
1807 buf.clear();
1808
1809 buf.put_u8(0x0A);
1811 encode_varint(buf, collection_name.len());
1812 buf.put_slice(collection_name.as_bytes());
1813
1814 buf.put_u8(0x10);
1816 buf.put_u8(1);
1817
1818 let selector_buf = encode_points_selector(point_ids);
1820 buf.put_u8(0x22);
1821 encode_varint(buf, selector_buf.len());
1822 buf.extend_from_slice(&selector_buf);
1823 Ok(())
1824}
1825
1826pub fn encode_delete_points_proto(
1828 buf: &mut BytesMut,
1829 collection_name: &str,
1830 point_ids: &[u64],
1831) -> QdrantResult<()> {
1832 let ids: Vec<crate::PointId> = point_ids
1833 .iter()
1834 .map(|&id| crate::PointId::Num(id))
1835 .collect();
1836 encode_delete_points_mixed_proto(buf, collection_name, &ids)
1837}
1838
1839fn encode_points_selector(ids: &[crate::PointId]) -> BytesMut {
1841 let mut ids_list = BytesMut::with_capacity(ids.len() * 40);
1843 for id in ids {
1844 let id_buf = encode_point_id_message(id);
1845 ids_list.put_u8(0x0A); encode_varint(&mut ids_list, id_buf.len());
1847 ids_list.extend_from_slice(&id_buf);
1848 }
1849
1850 let mut selector = BytesMut::with_capacity(ids_list.len() + 8);
1852 selector.put_u8(0x0A); encode_varint(&mut selector, ids_list.len());
1854 selector.extend_from_slice(&ids_list);
1855
1856 selector
1857}
1858
1859pub fn encode_list_collections_proto(buf: &mut BytesMut) {
1865 buf.clear();
1866 }
1868
1869pub fn encode_collection_info_proto(buf: &mut BytesMut, collection_name: &str) -> QdrantResult<()> {
1877 ensure_collection_name(collection_name)?;
1878 buf.clear();
1879 buf.put_u8(0x0A);
1880 encode_varint(buf, collection_name.len());
1881 buf.extend_from_slice(collection_name.as_bytes());
1882 Ok(())
1883}
1884
1885#[inline]
1891pub fn varint_len(value: u64) -> usize {
1892 if value == 0 {
1893 1
1894 } else {
1895 let bits = 64 - value.leading_zeros() as usize;
1896 bits.div_ceil(7)
1897 }
1898}
1899
1900#[cfg(test)]
1905mod tests {
1906 use super::*;
1907
1908 fn assert_encode_error<T: std::fmt::Debug>(result: QdrantResult<T>, expected: &str) {
1909 match result {
1910 Err(QdrantError::Encode(message)) => {
1911 assert!(
1912 message.contains(expected),
1913 "expected error containing {expected:?}, got {message:?}"
1914 );
1915 }
1916 other => panic!("expected encode error containing {expected:?}, got {other:?}"),
1917 }
1918 }
1919
1920 #[test]
1921 fn test_varint_encoding() {
1922 let mut buf = BytesMut::new();
1923
1924 encode_varint(&mut buf, 1);
1926 assert_eq!(&buf[..], &[0x01]);
1927
1928 buf.clear();
1929 encode_varint(&mut buf, 127);
1930 assert_eq!(&buf[..], &[0x7F]);
1931
1932 buf.clear();
1934 encode_varint(&mut buf, 128);
1935 assert_eq!(&buf[..], &[0x80, 0x01]);
1936
1937 buf.clear();
1938 encode_varint(&mut buf, 300);
1939 assert_eq!(&buf[..], &[0xAC, 0x02]);
1940 }
1941
1942 #[test]
1943 fn test_qdrant_filter_wire_tags_match_current_proto() {
1944 assert_eq!(FILTER_SHOULD, 0x0A);
1945 assert_eq!(FILTER_MUST, 0x12);
1946 assert_eq!(FILTER_MUST_NOT, 0x1A);
1947 assert_eq!(CONDITION_HAS_ID, 0x1A);
1948 assert_eq!(CONDITION_IS_NULL, 0x2A);
1949 }
1950
1951 #[test]
1952 fn test_encode_search_basic() {
1953 let mut buf = BytesMut::with_capacity(1024);
1954 let vector = vec![0.1f32, 0.2, 0.3, 0.4];
1955
1956 encode_search_proto(&mut buf, "test_collection", &vector, 10, None, None, false)
1957 .expect("search request should encode");
1958
1959 assert_eq!(buf[0], SEARCH_COLLECTION);
1961
1962 assert!(buf.len() > 20);
1964 }
1965
1966 #[test]
1967 fn test_encode_search_rejects_invalid_request_shape() {
1968 let mut buf = BytesMut::with_capacity(1024);
1969 let vector = vec![0.1f32, 0.2, 0.3, 0.4];
1970
1971 assert_encode_error(
1972 encode_search_proto(&mut buf, "", &vector, 10, None, None, false),
1973 "collection name",
1974 );
1975 assert_encode_error(
1976 encode_search_proto(&mut buf, "products", &[], 10, None, None, false),
1977 "search vector",
1978 );
1979 assert_encode_error(
1980 encode_search_proto(&mut buf, "products", &[f32::NAN], 10, None, None, false),
1981 "non-finite vector value",
1982 );
1983 assert_encode_error(
1984 encode_search_proto(&mut buf, "products", &vector, 0, None, None, false),
1985 "search limit",
1986 );
1987 assert_encode_error(
1988 encode_search_proto(
1989 &mut buf,
1990 "products",
1991 &vector,
1992 10,
1993 Some(f32::INFINITY),
1994 None,
1995 false,
1996 ),
1997 "score threshold",
1998 );
1999 assert_encode_error(
2000 encode_search_proto(&mut buf, "products", &vector, 10, None, Some(" "), false),
2001 "vector name",
2002 );
2003 }
2004
2005 #[test]
2006 fn test_encode_search_with_vectors_selector() {
2007 let mut buf = BytesMut::with_capacity(1024);
2008 let vector = vec![0.1f32, 0.2, 0.3, 0.4];
2009
2010 encode_search_proto(&mut buf, "test_collection", &vector, 10, None, None, true)
2011 .expect("search request should encode");
2012
2013 let selector_offset = buf
2014 .iter()
2015 .position(|tag| *tag == SEARCH_WITH_VECTORS)
2016 .expect("search request should include with_vectors field");
2017 assert_eq!(
2018 &buf[selector_offset..selector_offset + 4],
2019 &[SEARCH_WITH_VECTORS, 0x02, 0x08, 0x01]
2020 );
2021 }
2022
2023 #[test]
2024 fn test_encode_named_search_includes_only_named_vector_selector() {
2025 let mut buf = BytesMut::with_capacity(1024);
2026 let vector = vec![0.1f32, 0.2, 0.3, 0.4];
2027
2028 encode_search_proto(
2029 &mut buf,
2030 "test_collection",
2031 &vector,
2032 10,
2033 None,
2034 Some("image"),
2035 true,
2036 )
2037 .expect("named search request should encode");
2038
2039 let selector_offset = buf
2040 .iter()
2041 .position(|tag| *tag == SEARCH_WITH_VECTORS)
2042 .expect("named search request should include with_vectors field");
2043 assert_eq!(
2044 &buf[selector_offset..selector_offset + 11],
2045 &[
2046 SEARCH_WITH_VECTORS,
2047 0x09,
2048 0x12,
2049 0x07,
2050 0x0A,
2051 0x05,
2052 b'i',
2053 b'm',
2054 b'a',
2055 b'g',
2056 b'e'
2057 ]
2058 );
2059 }
2060
2061 #[test]
2062 fn test_zero_copy_vector() {
2063 let mut buf = BytesMut::with_capacity(1024);
2064 let vector = vec![1.0f32, 2.0, 3.0, 4.0];
2065
2066 encode_search_proto(&mut buf, "test", &vector, 5, None, None, false)
2067 .expect("search request should encode");
2068
2069 let vector_start = 8;
2075 let vector_bytes = &buf[vector_start..vector_start + 16];
2076
2077 let float_bytes: [u8; 4] = 1.0f32.to_le_bytes();
2079 assert_eq!(&vector_bytes[0..4], &float_bytes);
2080 }
2081
2082 #[test]
2083 fn test_varint_len() {
2084 assert_eq!(varint_len(0), 1);
2085 assert_eq!(varint_len(1), 1);
2086 assert_eq!(varint_len(127), 1);
2087 assert_eq!(varint_len(128), 2);
2088 assert_eq!(varint_len(16383), 2);
2089 assert_eq!(varint_len(16384), 3);
2090 }
2091
2092 #[test]
2093 fn test_encode_search_with_filter() {
2094 use qail_core::ast::{Condition, Expr, Operator, Value};
2095
2096 let mut buf = BytesMut::with_capacity(1024);
2097 let vector = vec![0.1f32, 0.2, 0.3];
2098 let conditions = vec![
2099 Condition {
2100 left: Expr::Named("category".to_string()),
2101 op: Operator::Eq,
2102 value: Value::String("electronics".to_string()),
2103 is_array_unnest: false,
2104 },
2105 Condition {
2106 left: Expr::Named("price".to_string()),
2107 op: Operator::Lt,
2108 value: Value::Int(1000),
2109 is_array_unnest: false,
2110 },
2111 ];
2112
2113 encode_search_with_filter_proto(
2114 &mut buf,
2115 SearchRequest {
2116 collection: "products",
2117 vector: &vector,
2118 limit: 10,
2119 score_threshold: None,
2120 vector_name: None,
2121 with_vectors: false,
2122 },
2123 &conditions,
2124 false,
2125 )
2126 .expect("filter encoding should succeed");
2127
2128 assert!(buf.len() > 50);
2130 assert_eq!(buf[0], SEARCH_COLLECTION);
2132 assert!(buf.contains(&SEARCH_FILTER));
2134 }
2135
2136 #[test]
2137 fn test_encode_filtered_search_with_vectors_selector() {
2138 use qail_core::ast::{Condition, Expr, Operator, Value};
2139
2140 let mut buf = BytesMut::with_capacity(1024);
2141 let vector = vec![0.1f32, 0.2, 0.3];
2142 let conditions = vec![Condition {
2143 left: Expr::Named("tenant_id".to_string()),
2144 op: Operator::Eq,
2145 value: Value::String("tenant-1".to_string()),
2146 is_array_unnest: false,
2147 }];
2148
2149 encode_search_with_filter_proto(
2150 &mut buf,
2151 SearchRequest {
2152 collection: "products",
2153 vector: &vector,
2154 limit: 10,
2155 score_threshold: None,
2156 vector_name: None,
2157 with_vectors: true,
2158 },
2159 &conditions,
2160 false,
2161 )
2162 .expect("filter encoding should succeed");
2163
2164 assert!(
2165 buf.contains(&SEARCH_WITH_VECTORS),
2166 "filtered search should preserve the with_vectors selector"
2167 );
2168 }
2169
2170 #[test]
2171 fn test_encode_upsert_rejects_invalid_points_and_payload() {
2172 let mut buf = BytesMut::with_capacity(1024);
2173
2174 assert_encode_error(
2175 encode_upsert_proto(&mut buf, "", &[crate::Point::new_num(1, vec![1.0])], true),
2176 "collection name",
2177 );
2178 assert_encode_error(
2179 encode_upsert_proto(&mut buf, "products", &[], true),
2180 "point list",
2181 );
2182 assert_encode_error(
2183 encode_upsert_proto(
2184 &mut buf,
2185 "products",
2186 &[crate::Point::new(
2187 crate::PointId::Uuid(" ".to_string()),
2188 vec![1.0],
2189 )],
2190 true,
2191 ),
2192 "upsert",
2193 );
2194 assert_encode_error(
2195 encode_upsert_proto(
2196 &mut buf,
2197 "products",
2198 &[crate::Point::new_num(1, vec![])],
2199 true,
2200 ),
2201 "upsert point 0 vector",
2202 );
2203 assert_encode_error(
2204 encode_upsert_proto(
2205 &mut buf,
2206 "products",
2207 &[crate::Point::new_num(1, vec![f32::INFINITY])],
2208 true,
2209 ),
2210 "non-finite vector value",
2211 );
2212
2213 let point_with_blank_key = crate::Point::new_num(1, vec![1.0])
2214 .with_payload(" ", crate::point::PayloadValue::String("bad".to_string()));
2215 assert_encode_error(
2216 encode_upsert_proto(&mut buf, "products", &[point_with_blank_key], true),
2217 "payload field name",
2218 );
2219
2220 let mut nested = crate::point::Payload::new();
2221 nested.insert(
2222 "".to_string(),
2223 crate::point::PayloadValue::String("bad".to_string()),
2224 );
2225 let point_with_nested_blank_key = crate::Point::new_num(1, vec![1.0])
2226 .with_payload("meta", crate::point::PayloadValue::Object(nested));
2227 assert_encode_error(
2228 encode_upsert_proto(&mut buf, "products", &[point_with_nested_blank_key], true),
2229 "payload field name",
2230 );
2231
2232 let point_with_bad_float = crate::Point::new_num(1, vec![1.0]).with_payload(
2233 "score",
2234 crate::point::PayloadValue::List(vec![crate::point::PayloadValue::Float(f64::NAN)]),
2235 );
2236 assert_encode_error(
2237 encode_upsert_proto(&mut buf, "products", &[point_with_bad_float], true),
2238 "payload float",
2239 );
2240 }
2241
2242 #[test]
2243 fn test_encode_get_points() {
2244 let mut buf = BytesMut::with_capacity(1024);
2245 let ids = vec![
2246 crate::PointId::Num(42),
2247 crate::PointId::Uuid("abc-123".to_string()),
2248 ];
2249
2250 encode_get_points_proto(&mut buf, "my_collection", &ids, true)
2251 .expect("get request should encode");
2252
2253 assert_eq!(buf[0], 0x0A); assert!(buf.len() > 20);
2255 }
2256
2257 #[test]
2258 fn test_encode_point_selector_requests_reject_empty_or_blank_ids() {
2259 let mut buf = BytesMut::with_capacity(1024);
2260 let blank_id = vec![crate::PointId::Uuid(" ".to_string())];
2261 let good_id = vec![crate::PointId::Num(1)];
2262 let mut payload = crate::point::Payload::new();
2263 payload.insert(
2264 "name".to_string(),
2265 crate::point::PayloadValue::String("x".to_string()),
2266 );
2267
2268 assert_encode_error(
2269 encode_get_points_proto(&mut buf, "products", &[], false),
2270 "point id list",
2271 );
2272 assert_encode_error(
2273 encode_get_points_proto(&mut buf, "products", &blank_id, false),
2274 "get",
2275 );
2276 assert_encode_error(
2277 encode_delete_points_mixed_proto(&mut buf, "products", &[]),
2278 "point id list",
2279 );
2280 assert_encode_error(
2281 encode_delete_points_mixed_proto(&mut buf, "products", &blank_id),
2282 "delete",
2283 );
2284 assert_encode_error(
2285 encode_set_payload_proto(&mut buf, "products", &[], &payload, true),
2286 "point id list",
2287 );
2288 assert_encode_error(
2289 encode_set_payload_proto(
2290 &mut buf,
2291 "products",
2292 &good_id,
2293 &crate::point::Payload::new(),
2294 true,
2295 ),
2296 "payload update",
2297 );
2298 assert_encode_error(
2299 encode_scroll_points_proto(&mut buf, "products", 0, None, false),
2300 "scroll limit",
2301 );
2302 assert_encode_error(
2303 encode_scroll_points_proto(&mut buf, "products", 10, Some(&blank_id[0]), false),
2304 "scroll offset",
2305 );
2306 }
2307
2308 #[test]
2309 fn test_encode_scroll_points() {
2310 let mut buf = BytesMut::with_capacity(1024);
2311
2312 encode_scroll_points_proto(&mut buf, "my_collection", 100, None, false)
2313 .expect("scroll request should encode");
2314
2315 assert_eq!(buf[0], 0x0A);
2316 assert!(buf.len() > 10);
2317 }
2318
2319 #[test]
2320 fn test_encode_filtered_scroll_points() {
2321 use qail_core::ast::{Condition, Expr, Operator, Value};
2322
2323 let mut buf = BytesMut::with_capacity(1024);
2324 let must = vec![Condition {
2325 left: Expr::Named("tenant_id".to_string()),
2326 op: Operator::Eq,
2327 value: Value::String("tenant-1".to_string()),
2328 is_array_unnest: false,
2329 }];
2330
2331 encode_scroll_points_with_filter_grouped_cages_proto(
2332 &mut buf,
2333 "my_collection",
2334 100,
2335 None,
2336 false,
2337 &must,
2338 &[],
2339 )
2340 .expect("filtered scroll should encode");
2341
2342 assert_eq!(buf[0], SCROLL_COLLECTION);
2343 assert!(
2344 buf.contains(&SCROLL_FILTER),
2345 "filtered scroll should include filter field"
2346 );
2347 }
2348
2349 #[test]
2350 fn test_encode_delete_points_uuid() {
2351 let mut buf = BytesMut::with_capacity(1024);
2352 let ids = vec![
2353 crate::PointId::Uuid("test-uuid-1".to_string()),
2354 crate::PointId::Num(99),
2355 ];
2356
2357 encode_delete_points_mixed_proto(&mut buf, "products", &ids)
2358 .expect("delete request should encode");
2359
2360 assert_eq!(buf[0], 0x0A); assert!(buf.len() > 20);
2362 }
2363
2364 #[test]
2365 fn test_encode_set_payload() {
2366 let mut buf = BytesMut::with_capacity(1024);
2367 let ids = vec![crate::PointId::Num(1)];
2368 let mut payload = crate::point::Payload::new();
2369 payload.insert(
2370 "name".to_string(),
2371 crate::point::PayloadValue::String("updated".to_string()),
2372 );
2373
2374 encode_set_payload_proto(&mut buf, "my_col", &ids, &payload, true)
2375 .expect("set payload request should encode");
2376
2377 assert_eq!(buf[0], 0x0A);
2378 assert!(buf.len() > 15);
2379 }
2380
2381 #[test]
2382 fn test_encode_search_with_filter_rejects_unsupported_operator() {
2383 use qail_core::ast::{Condition, Expr, Operator, Value};
2384
2385 let mut buf = BytesMut::with_capacity(512);
2386 let vector = vec![0.1f32, 0.2];
2387 let conditions = vec![Condition {
2388 left: Expr::Named("status".to_string()),
2389 op: Operator::NotILike,
2390 value: Value::String("%inactive%".to_string()),
2391 is_array_unnest: false,
2392 }];
2393
2394 let err = encode_search_with_filter_proto(
2395 &mut buf,
2396 SearchRequest {
2397 collection: "products",
2398 vector: &vector,
2399 limit: 5,
2400 score_threshold: None,
2401 vector_name: None,
2402 with_vectors: false,
2403 },
2404 &conditions,
2405 false,
2406 )
2407 .expect_err("unsupported operator must return an explicit error");
2408
2409 match err {
2410 QdrantError::Encode(message) => {
2411 assert!(message.contains("Unsupported Qdrant filter condition"));
2412 }
2413 other => panic!("expected encode error, got {:?}", other),
2414 }
2415 }
2416
2417 #[test]
2418 fn test_encode_search_with_filter_supports_is_null() {
2419 use qail_core::ast::{Condition, Expr, Operator, Value};
2420
2421 for value in [Value::Null, Value::NullUuid] {
2422 let mut buf = BytesMut::with_capacity(512);
2423 let vector = vec![0.1f32, 0.2];
2424 let conditions = vec![Condition {
2425 left: Expr::Named("tenant_id".to_string()),
2426 op: Operator::IsNull,
2427 value,
2428 is_array_unnest: false,
2429 }];
2430
2431 encode_search_with_filter_proto(
2432 &mut buf,
2433 SearchRequest {
2434 collection: "products",
2435 vector: &vector,
2436 limit: 5,
2437 score_threshold: None,
2438 vector_name: None,
2439 with_vectors: false,
2440 },
2441 &conditions,
2442 false,
2443 )
2444 .expect("IS NULL filters should encode as Qdrant IsNullCondition");
2445
2446 assert!(
2447 buf.contains(&CONDITION_IS_NULL),
2448 "encoded request should contain an IsNullCondition"
2449 );
2450 }
2451 }
2452
2453 #[test]
2454 fn test_encode_search_with_filter_uses_has_id_for_point_id() {
2455 use qail_core::ast::{Condition, Expr, Operator, Value};
2456
2457 let condition = Condition {
2458 left: Expr::Named("ID".to_string()),
2459 op: Operator::Eq,
2460 value: Value::Int(42),
2461 is_array_unnest: false,
2462 };
2463
2464 let encoded = encode_condition_message(&condition).expect("id filter should encode");
2465
2466 assert_eq!(encoded[0], CONDITION_HAS_ID);
2467 assert!(
2468 encoded
2469 .windows(2)
2470 .any(|window| window == [POINT_ID_NUM, 42]),
2471 "encoded HasIdCondition should contain numeric PointId"
2472 );
2473
2474 let condition = Condition {
2475 left: Expr::Named("id".to_string()),
2476 op: Operator::In,
2477 value: Value::Array(vec![
2478 Value::Int(42),
2479 Value::String("uuid-like-id".to_string()),
2480 ]),
2481 is_array_unnest: false,
2482 };
2483
2484 let encoded = encode_condition_message(&condition).expect("id IN filter should encode");
2485
2486 assert_eq!(encoded[0], CONDITION_HAS_ID);
2487 assert!(
2488 encoded
2489 .windows(2)
2490 .any(|window| window == [POINT_ID_NUM, 42])
2491 );
2492 assert!(
2493 encoded
2494 .windows("uuid-like-id".len())
2495 .any(|window| window == b"uuid-like-id")
2496 );
2497 }
2498
2499 #[test]
2500 fn test_encode_search_with_filter_rejects_invalid_point_id_filter() {
2501 use qail_core::ast::{Condition, Expr, Operator, Value};
2502
2503 let condition = Condition {
2504 left: Expr::Named("id".to_string()),
2505 op: Operator::Eq,
2506 value: Value::Float(1.5),
2507 is_array_unnest: false,
2508 };
2509
2510 let err = encode_condition_message(&condition)
2511 .expect_err("float id filters must not encode as payload filters");
2512
2513 match err {
2514 QdrantError::Encode(message) => {
2515 assert!(message.contains("id filters support only"));
2516 }
2517 other => panic!("expected encode error, got {:?}", other),
2518 }
2519
2520 let condition = Condition {
2521 left: Expr::Named("id".to_string()),
2522 op: Operator::In,
2523 value: Value::Array(vec![]),
2524 is_array_unnest: false,
2525 };
2526 assert_encode_error(encode_condition_message(&condition), "id IN filters");
2527 }
2528
2529 #[test]
2530 fn test_encode_search_with_filter_rejects_invalid_values() {
2531 use qail_core::ast::{Condition, Expr, Operator, Value};
2532
2533 let mut buf = BytesMut::with_capacity(512);
2534 let vector = vec![0.1f32, 0.2];
2535
2536 let unsupported_float_match = vec![Condition {
2537 left: Expr::Named("score".to_string()),
2538 op: Operator::Eq,
2539 value: Value::Float(1.5),
2540 is_array_unnest: false,
2541 }];
2542 assert_encode_error(
2543 encode_search_with_filter_proto(
2544 &mut buf,
2545 SearchRequest {
2546 collection: "products",
2547 vector: &vector,
2548 limit: 5,
2549 score_threshold: None,
2550 vector_name: None,
2551 with_vectors: false,
2552 },
2553 &unsupported_float_match,
2554 false,
2555 ),
2556 "Unsupported Qdrant filter condition",
2557 );
2558
2559 let non_finite_range = vec![Condition {
2560 left: Expr::Named("score".to_string()),
2561 op: Operator::Gt,
2562 value: Value::Float(f64::INFINITY),
2563 is_array_unnest: false,
2564 }];
2565 assert_encode_error(
2566 encode_search_with_filter_proto(
2567 &mut buf,
2568 SearchRequest {
2569 collection: "products",
2570 vector: &vector,
2571 limit: 5,
2572 score_threshold: None,
2573 vector_name: None,
2574 with_vectors: false,
2575 },
2576 &non_finite_range,
2577 false,
2578 ),
2579 "filter range float",
2580 );
2581
2582 let empty_text = vec![Condition {
2583 left: Expr::Named("description".to_string()),
2584 op: Operator::Contains,
2585 value: Value::String("".to_string()),
2586 is_array_unnest: false,
2587 }];
2588 assert_encode_error(
2589 encode_search_with_filter_proto(
2590 &mut buf,
2591 SearchRequest {
2592 collection: "products",
2593 vector: &vector,
2594 limit: 5,
2595 score_threshold: None,
2596 vector_name: None,
2597 with_vectors: false,
2598 },
2599 &empty_text,
2600 false,
2601 ),
2602 "text filter value",
2603 );
2604
2605 let empty_id = Condition {
2606 left: Expr::Named("id".to_string()),
2607 op: Operator::Eq,
2608 value: Value::String(" ".to_string()),
2609 is_array_unnest: false,
2610 };
2611 assert_encode_error(encode_condition_message(&empty_id), "id filter");
2612 }
2613
2614 #[test]
2615 fn test_encode_search_with_filter_uses_current_match_wire_tags() {
2616 use qail_core::ast::{Condition, Expr, Operator, Value};
2617
2618 let bool_condition = Condition {
2619 left: Expr::Named("archived".to_string()),
2620 op: Operator::Eq,
2621 value: Value::Bool(false),
2622 is_array_unnest: false,
2623 };
2624 let encoded = encode_condition_message(&bool_condition).expect("bool match should encode");
2625 assert!(
2626 encoded.windows(2).any(|window| window == [0x18, 0x00]),
2627 "bool match should use Match.boolean field 3"
2628 );
2629
2630 let text_condition = Condition {
2631 left: Expr::Named("summary".to_string()),
2632 op: Operator::Contains,
2633 value: Value::String("refund".to_string()),
2634 is_array_unnest: false,
2635 };
2636 let encoded = encode_condition_message(&text_condition).expect("text match should encode");
2637 assert!(
2638 encoded
2639 .windows(8)
2640 .any(|window| window == [0x22, 0x06, b'r', b'e', b'f', b'u', b'n', b'd']),
2641 "text match should use Match.text field 4"
2642 );
2643 }
2644
2645 #[test]
2646 fn test_encode_search_with_filter_supports_uuid_payload_keywords() {
2647 use qail_core::ast::{Condition, Expr, Operator, Value};
2648
2649 let owner_id = uuid::Uuid::parse_str("aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa").unwrap();
2650 let owner_condition = Condition {
2651 left: Expr::Named("owner_id".to_string()),
2652 op: Operator::Eq,
2653 value: Value::Uuid(owner_id),
2654 is_array_unnest: false,
2655 };
2656 let encoded =
2657 encode_condition_message(&owner_condition).expect("uuid payload match should encode");
2658 let owner_id = owner_id.to_string();
2659 assert!(
2660 encoded
2661 .windows(36)
2662 .any(|window| window == owner_id.as_bytes()),
2663 "uuid equality should encode as a keyword string"
2664 );
2665
2666 let reviewer_id = uuid::Uuid::parse_str("bbbbbbbb-bbbb-4bbb-bbbb-bbbbbbbbbbbb").unwrap();
2667 let reviewer_condition = Condition {
2668 left: Expr::Named("reviewer_id".to_string()),
2669 op: Operator::In,
2670 value: Value::Array(vec![
2671 Value::Uuid(reviewer_id),
2672 Value::String("external-reviewer".to_string()),
2673 ]),
2674 is_array_unnest: false,
2675 };
2676 let encoded =
2677 encode_condition_message(&reviewer_condition).expect("uuid IN match should encode");
2678 let reviewer_id = reviewer_id.to_string();
2679 assert!(
2680 encoded.contains(&0x2A),
2681 "uuid IN should use Match.keywords field 5"
2682 );
2683 assert!(
2684 encoded
2685 .windows(36)
2686 .any(|window| window == reviewer_id.as_bytes()),
2687 "uuid IN should include the UUID keyword"
2688 );
2689 assert!(
2690 encoded
2691 .windows("external-reviewer".len())
2692 .any(|window| window == b"external-reviewer"),
2693 "uuid IN should allow mixed keyword strings"
2694 );
2695 }
2696
2697 #[test]
2698 fn test_encode_search_with_filter_supports_native_in() {
2699 use qail_core::ast::{Condition, Expr, Operator, Value};
2700
2701 let string_condition = Condition {
2702 left: Expr::Named("status".to_string()),
2703 op: Operator::In,
2704 value: Value::Array(vec![
2705 Value::String("open".to_string()),
2706 Value::String("closed".to_string()),
2707 ]),
2708 is_array_unnest: false,
2709 };
2710 let encoded =
2711 encode_condition_message(&string_condition).expect("string IN match should encode");
2712 assert!(
2713 encoded.contains(&0x2A),
2714 "string IN should use Match.keywords field 5"
2715 );
2716 assert!(
2717 encoded.windows(4).any(|window| window == b"open"),
2718 "string IN should contain first keyword"
2719 );
2720
2721 let int_condition = Condition {
2722 left: Expr::Named("priority".to_string()),
2723 op: Operator::In,
2724 value: Value::Array(vec![Value::Int(1), Value::Int(2)]),
2725 is_array_unnest: false,
2726 };
2727 let encoded = encode_condition_message(&int_condition).expect("int IN match should encode");
2728 assert!(
2729 encoded.contains(&0x32),
2730 "integer IN should use Match.integers field 6"
2731 );
2732
2733 for bad in [
2734 Value::Array(vec![]),
2735 Value::Array(vec![Value::String("open".to_string()), Value::Int(1)]),
2736 Value::Array(vec![Value::Bool(true)]),
2737 ] {
2738 let condition = Condition {
2739 left: Expr::Named("status".to_string()),
2740 op: Operator::In,
2741 value: bad,
2742 is_array_unnest: false,
2743 };
2744 assert_encode_error(encode_condition_message(&condition), "IN filters");
2745 }
2746 }
2747
2748 #[test]
2749 fn test_encode_search_with_filter_supports_native_negative_filters() {
2750 use qail_core::ast::{Condition, Expr, Operator, Value};
2751
2752 for condition in [
2753 Condition {
2754 left: Expr::Named("status".to_string()),
2755 op: Operator::Ne,
2756 value: Value::String("deleted".to_string()),
2757 is_array_unnest: false,
2758 },
2759 Condition {
2760 left: Expr::Named("priority".to_string()),
2761 op: Operator::NotIn,
2762 value: Value::Array(vec![Value::Int(1), Value::Int(2)]),
2763 is_array_unnest: false,
2764 },
2765 Condition {
2766 left: Expr::Named("deleted_at".to_string()),
2767 op: Operator::IsNotNull,
2768 value: Value::Null,
2769 is_array_unnest: false,
2770 },
2771 Condition {
2772 left: Expr::Named("summary".to_string()),
2773 op: Operator::NotLike,
2774 value: Value::String("refund".to_string()),
2775 is_array_unnest: false,
2776 },
2777 Condition {
2778 left: Expr::Named("id".to_string()),
2779 op: Operator::NotIn,
2780 value: Value::Array(vec![
2781 Value::Int(42),
2782 Value::String("uuid-like-id".to_string()),
2783 ]),
2784 is_array_unnest: false,
2785 },
2786 ] {
2787 let encoded =
2788 encode_condition_message(&condition).expect("negative filter should encode");
2789
2790 assert_eq!(encoded[0], CONDITION_FILTER);
2791 assert!(
2792 encoded.contains(&FILTER_MUST_NOT),
2793 "negative filter must use Filter.must_not"
2794 );
2795 }
2796 }
2797
2798 #[test]
2799 fn test_encode_search_with_filter_grouped_cages_includes_nested_filter_conditions() {
2800 use qail_core::ast::{Condition, Expr, Operator, Value};
2801
2802 let mut buf = BytesMut::with_capacity(1024);
2803 let vector = vec![0.1f32, 0.2, 0.3];
2804 let must_conditions = vec![Condition {
2805 left: Expr::Named("tenant_id".to_string()),
2806 op: Operator::Eq,
2807 value: Value::String("t1".to_string()),
2808 is_array_unnest: false,
2809 }];
2810 let should_groups = vec![
2811 vec![
2812 Condition {
2813 left: Expr::Named("city".to_string()),
2814 op: Operator::Eq,
2815 value: Value::String("London".to_string()),
2816 is_array_unnest: false,
2817 },
2818 Condition {
2819 left: Expr::Named("city".to_string()),
2820 op: Operator::Eq,
2821 value: Value::String("Paris".to_string()),
2822 is_array_unnest: false,
2823 },
2824 ],
2825 vec![
2826 Condition {
2827 left: Expr::Named("country".to_string()),
2828 op: Operator::Eq,
2829 value: Value::String("UK".to_string()),
2830 is_array_unnest: false,
2831 },
2832 Condition {
2833 left: Expr::Named("country".to_string()),
2834 op: Operator::Eq,
2835 value: Value::String("FR".to_string()),
2836 is_array_unnest: false,
2837 },
2838 ],
2839 ];
2840
2841 encode_search_with_filter_grouped_cages_proto(
2842 &mut buf,
2843 SearchRequest {
2844 collection: "products",
2845 vector: &vector,
2846 limit: 10,
2847 score_threshold: None,
2848 vector_name: None,
2849 with_vectors: false,
2850 },
2851 &must_conditions,
2852 &should_groups,
2853 )
2854 .expect("grouped-cage filter encoding should succeed");
2855
2856 assert!(buf.contains(&SEARCH_FILTER));
2857 assert!(
2858 buf.contains(&CONDITION_FILTER),
2859 "expected nested filter condition tag for OR groups"
2860 );
2861 }
2862
2863 #[test]
2864 fn test_encode_create_field_index() {
2865 let mut buf = BytesMut::with_capacity(256);
2866
2867 encode_create_field_index_proto(&mut buf, "products", "category", FieldType::Keyword, true)
2868 .expect("field index request should encode");
2869
2870 assert_eq!(buf[0], 0x0A);
2871 assert!(buf.len() > 10);
2872 }
2873
2874 #[test]
2875 fn test_encode_collection_and_index_requests_reject_invalid_shape() {
2876 let mut buf = BytesMut::with_capacity(256);
2877
2878 assert_encode_error(
2879 encode_create_collection_proto(&mut buf, "", 128, crate::Distance::Cosine, false),
2880 "collection name",
2881 );
2882 assert_encode_error(
2883 encode_create_collection_proto(&mut buf, "products", 0, crate::Distance::Cosine, false),
2884 "vector_size",
2885 );
2886 assert_encode_error(
2887 encode_delete_collection_proto(&mut buf, " "),
2888 "collection name",
2889 );
2890 assert_encode_error(
2891 encode_collection_info_proto(&mut buf, ""),
2892 "collection name",
2893 );
2894 assert_encode_error(
2895 encode_create_field_index_proto(&mut buf, "products", "", FieldType::Keyword, true),
2896 "payload field name",
2897 );
2898 }
2899
2900 #[test]
2901 fn test_encode_payload_value_string() {
2902 let val = crate::point::PayloadValue::String("hello".to_string());
2903 let buf = encode_payload_value(&val).expect("payload value should encode");
2904
2905 assert_eq!(buf[0], 0x22);
2907 assert!(buf.len() > 5);
2908 }
2909
2910 #[test]
2911 fn test_encode_payload_value_integer() {
2912 let val = crate::point::PayloadValue::Integer(42);
2913 let buf = encode_payload_value(&val).expect("payload value should encode");
2914
2915 assert_eq!(buf[0], 0x18);
2917 }
2918}