1use crate::error::QdrantResult;
7use crate::point::{PayloadValue, Point, PointId, ScoredPoint};
8use serde_json::{json, Value as JsonValue};
9
10pub fn encode_search_request(
25 vector: &[f32],
26 limit: u64,
27 offset: Option<u64>,
28 score_threshold: Option<f32>,
29 with_vector: bool,
30) -> Vec<u8> {
31 let mut request = json!({
32 "vector": vector,
33 "limit": limit,
34 "with_payload": true,
35 "with_vector": with_vector,
36 });
37
38 if let Some(off) = offset {
39 request["offset"] = json!(off);
40 }
41
42 if let Some(threshold) = score_threshold {
43 request["score_threshold"] = json!(threshold);
44 }
45
46 serde_json::to_vec(&request).unwrap_or_default()
47}
48
49pub fn encode_search_request_with_filter(
51 vector: &[f32],
52 limit: u64,
53 offset: Option<u64>,
54 score_threshold: Option<f32>,
55 with_vector: bool,
56 filter: JsonValue,
57) -> Vec<u8> {
58 let mut request = json!({
59 "vector": vector,
60 "limit": limit,
61 "with_payload": true,
62 "with_vector": with_vector,
63 "filter": filter,
64 });
65
66 if let Some(off) = offset {
67 request["offset"] = json!(off);
68 }
69
70 if let Some(threshold) = score_threshold {
71 request["score_threshold"] = json!(threshold);
72 }
73
74 serde_json::to_vec(&request).unwrap_or_default()
75}
76
77pub fn encode_upsert_request(points: &[Point]) -> Vec<u8> {
90 let points_json: Vec<JsonValue> = points
91 .iter()
92 .map(|p| {
93 let id = match &p.id {
94 PointId::Uuid(s) => json!(s),
95 PointId::Num(n) => json!(n),
96 };
97
98 let payload: JsonValue = p.payload
99 .iter()
100 .map(|(k, v)| (k.clone(), payload_value_to_json(v)))
101 .collect();
102
103 json!({
104 "id": id,
105 "vector": p.vector,
106 "payload": payload,
107 })
108 })
109 .collect();
110
111 let request = json!({ "points": points_json });
112 serde_json::to_vec(&request).unwrap_or_default()
113}
114
115pub fn encode_upsert_multi_vector_request(points: &[crate::point::MultiVectorPoint]) -> Vec<u8> {
119 use crate::point::MultiVectorPoint;
120
121 let points_json: Vec<JsonValue> = points
122 .iter()
123 .map(|p: &MultiVectorPoint| {
124 let id = match &p.id {
125 PointId::Uuid(s) => json!(s),
126 PointId::Num(n) => json!(n),
127 };
128
129 let payload: JsonValue = p.payload
130 .iter()
131 .map(|(k, v)| (k.clone(), payload_value_to_json(v)))
132 .collect();
133
134 let vectors: JsonValue = p.vectors
136 .iter()
137 .map(|(k, v)| (k.clone(), json!(v)))
138 .collect();
139
140 json!({
141 "id": id,
142 "vector": vectors,
143 "payload": payload,
144 })
145 })
146 .collect();
147
148 let request = json!({ "points": points_json });
149 serde_json::to_vec(&request).unwrap_or_default()
150}
151
152pub fn encode_delete_request(ids: &[PointId]) -> Vec<u8> {
161 let ids_json: Vec<JsonValue> = ids
162 .iter()
163 .map(|id| match id {
164 PointId::Uuid(s) => json!(s),
165 PointId::Num(n) => json!(n),
166 })
167 .collect();
168
169 let request = json!({ "points": ids_json });
170 serde_json::to_vec(&request).unwrap_or_default()
171}
172
173pub fn encode_create_collection_request(
177 vector_size: u64,
178 distance: &str, ) -> Vec<u8> {
180 let request = json!({
181 "vectors": {
182 "size": vector_size,
183 "distance": distance,
184 }
185 });
186 serde_json::to_vec(&request).unwrap_or_default()
187}
188
189pub fn encode_conditions_to_filter(conditions: &[qail_core::ast::Condition], is_or: bool) -> JsonValue {
207 use qail_core::ast::{Expr, Operator, Value};
208
209 let clauses: Vec<JsonValue> = conditions
210 .iter()
211 .filter_map(|cond| {
212 let key = match &cond.left {
214 Expr::Named(name) => name.clone(),
215 Expr::Aliased { name, .. } => name.clone(),
216 _ => return None,
217 };
218
219 let clause = match (&cond.op, &cond.value) {
221 (Operator::Eq, Value::String(s)) => json!({
223 "key": key,
224 "match": { "value": s }
225 }),
226 (Operator::Eq, Value::Int(n)) => json!({
227 "key": key,
228 "match": { "value": n }
229 }),
230 (Operator::Eq, Value::Bool(b)) => json!({
231 "key": key,
232 "match": { "value": b }
233 }),
234
235 (Operator::Gt, Value::Int(n)) => json!({
237 "key": key,
238 "range": { "gt": n }
239 }),
240 (Operator::Gt, Value::Float(f)) => json!({
241 "key": key,
242 "range": { "gt": f }
243 }),
244 (Operator::Gte, Value::Int(n)) => json!({
245 "key": key,
246 "range": { "gte": n }
247 }),
248 (Operator::Gte, Value::Float(f)) => json!({
249 "key": key,
250 "range": { "gte": f }
251 }),
252 (Operator::Lt, Value::Int(n)) => json!({
253 "key": key,
254 "range": { "lt": n }
255 }),
256 (Operator::Lt, Value::Float(f)) => json!({
257 "key": key,
258 "range": { "lt": f }
259 }),
260 (Operator::Lte, Value::Int(n)) => json!({
261 "key": key,
262 "range": { "lte": n }
263 }),
264 (Operator::Lte, Value::Float(f)) => json!({
265 "key": key,
266 "range": { "lte": f }
267 }),
268
269 (Operator::In, Value::Array(arr)) => {
271 let values: Vec<JsonValue> = arr.iter().filter_map(value_to_json).collect();
272 json!({
273 "key": key,
274 "match": { "any": values }
275 })
276 },
277
278 (Operator::IsNull, _) => json!({
280 "is_null": { "key": key }
281 }),
282 (Operator::IsNotNull, _) => json!({
283 "is_empty": { "key": key, "is_empty": false }
284 }),
285
286 (Operator::Contains | Operator::Like, Value::String(s)) => json!({
288 "key": key,
289 "match": { "text": s }
290 }),
291
292 (_, Value::String(s)) => json!({
294 "key": key,
295 "match": { "value": s }
296 }),
297 (_, Value::Int(n)) => json!({
298 "key": key,
299 "match": { "value": n }
300 }),
301
302 _ => return None,
303 };
304
305 Some(clause)
306 })
307 .collect();
308
309 if is_or {
311 json!({ "should": clauses })
312 } else {
313 json!({ "must": clauses })
314 }
315}
316
317fn value_to_json(value: &qail_core::ast::Value) -> Option<JsonValue> {
319 use qail_core::ast::Value;
320 match value {
321 Value::String(s) => Some(json!(s)),
322 Value::Int(n) => Some(json!(n)),
323 Value::Float(f) => Some(json!(f)),
324 Value::Bool(b) => Some(json!(b)),
325 Value::Null => Some(JsonValue::Null),
326 _ => None,
327 }
328}
329
330pub fn decode_search_response(data: &[u8]) -> QdrantResult<Vec<ScoredPoint>> {
332 let response: JsonValue = serde_json::from_slice(data)
333 .map_err(|e| crate::error::QdrantError::Decode(e.to_string()))?;
334
335 let results = response["result"]
336 .as_array()
337 .ok_or_else(|| crate::error::QdrantError::Decode("Missing 'result' array".to_string()))?;
338
339 let scored_points: Vec<ScoredPoint> = results
340 .iter()
341 .filter_map(|item| {
342 let id = parse_point_id(&item["id"])?;
343 let score = item["score"].as_f64()? as f32;
344 let payload = parse_payload(&item["payload"]);
345 let vector = item["vector"]
346 .as_array()
347 .map(|arr| arr.iter().filter_map(|v| v.as_f64().map(|f| f as f32)).collect());
348
349 Some(ScoredPoint { id, score, payload, vector })
350 })
351 .collect();
352
353 Ok(scored_points)
354}
355
356pub fn parse_point_id(value: &JsonValue) -> Option<PointId> {
358 if let Some(s) = value.as_str() {
359 Some(PointId::Uuid(s.to_string()))
360 } else {
361 value.as_u64().map(PointId::Num)
362 }
363}
364
365pub fn parse_payload(value: &JsonValue) -> crate::point::Payload {
367 let mut payload = crate::point::Payload::new();
368
369 if let Some(obj) = value.as_object() {
370 for (k, v) in obj {
371 if let Some(pv) = json_to_payload_value(v) {
372 payload.insert(k.clone(), pv);
373 }
374 }
375 }
376
377 payload
378}
379
380fn payload_value_to_json(value: &PayloadValue) -> JsonValue {
382 match value {
383 PayloadValue::String(s) => json!(s),
384 PayloadValue::Integer(n) => json!(n),
385 PayloadValue::Float(f) => json!(f),
386 PayloadValue::Bool(b) => json!(b),
387 PayloadValue::List(arr) => {
388 JsonValue::Array(arr.iter().map(payload_value_to_json).collect())
389 }
390 PayloadValue::Object(obj) => {
391 JsonValue::Object(obj.iter().map(|(k, v)| (k.clone(), payload_value_to_json(v))).collect())
392 }
393 PayloadValue::Null => JsonValue::Null,
394 }
395}
396
397fn json_to_payload_value(value: &JsonValue) -> Option<PayloadValue> {
399 match value {
400 JsonValue::Null => Some(PayloadValue::Null),
401 JsonValue::Bool(b) => Some(PayloadValue::Bool(*b)),
402 JsonValue::Number(n) => {
403 if let Some(i) = n.as_i64() {
404 Some(PayloadValue::Integer(i))
405 } else {
406 n.as_f64().map(PayloadValue::Float)
407 }
408 }
409 JsonValue::String(s) => Some(PayloadValue::String(s.clone())),
410 JsonValue::Array(arr) => {
411 let items: Vec<PayloadValue> = arr.iter().filter_map(json_to_payload_value).collect();
412 Some(PayloadValue::List(items))
413 }
414 JsonValue::Object(obj) => {
415 let map: std::collections::HashMap<String, PayloadValue> = obj
416 .iter()
417 .filter_map(|(k, v)| json_to_payload_value(v).map(|pv| (k.clone(), pv)))
418 .collect();
419 Some(PayloadValue::Object(map))
420 }
421 }
422}
423
424#[cfg(test)]
425mod tests {
426 use super::*;
427
428 #[test]
429 fn test_encode_search_request() {
430 let vector = vec![0.1, 0.2, 0.3];
431 let json_bytes = encode_search_request(&vector, 10, None, None, false);
432 let json: JsonValue = serde_json::from_slice(&json_bytes).unwrap();
433
434 assert!(json["vector"].is_array());
436 assert_eq!(json["limit"], 10);
437 assert_eq!(json["with_payload"], true);
438
439 assert_eq!(json["vector"].as_array().unwrap().len(), 3);
441 }
442
443 #[test]
444 fn test_encode_upsert_request() {
445 let point = Point::new("test-id", vec![0.5, 0.5]);
446 let json_bytes = encode_upsert_request(&[point]);
447 let json_str = String::from_utf8(json_bytes).unwrap();
448
449 assert!(json_str.contains("\"points\""));
450 assert!(json_str.contains("\"test-id\""));
451 assert!(json_str.contains("[0.5,0.5]"));
452 }
453
454 #[test]
455 fn test_encode_delete_request() {
456 let ids = vec![PointId::Uuid("id1".to_string()), PointId::Num(42)];
457 let json_bytes = encode_delete_request(&ids);
458 let json_str = String::from_utf8(json_bytes).unwrap();
459
460 assert!(json_str.contains("\"id1\""));
461 assert!(json_str.contains("42"));
462 }
463
464 #[test]
465 fn test_decode_search_response() {
466 let response = r#"{
467 "result": [
468 {"id": "abc", "score": 0.95, "payload": {"name": "test"}},
469 {"id": 123, "score": 0.80, "payload": {}}
470 ]
471 }"#;
472
473 let results = decode_search_response(response.as_bytes()).unwrap();
474 assert_eq!(results.len(), 2);
475 assert_eq!(results[0].score, 0.95);
476 assert_eq!(results[1].score, 0.80);
477 }
478
479 #[test]
480 fn test_encode_conditions_to_filter() {
481 use qail_core::ast::{Condition, Expr, Operator, Value};
482
483 let conditions = vec![
484 Condition {
485 left: Expr::Named("category".to_string()),
486 op: Operator::Eq,
487 value: Value::String("electronics".to_string()),
488 is_array_unnest: false,
489 },
490 Condition {
491 left: Expr::Named("price".to_string()),
492 op: Operator::Lt,
493 value: Value::Int(1000),
494 is_array_unnest: false,
495 },
496 ];
497
498 let filter = encode_conditions_to_filter(&conditions, false);
499
500 assert!(filter["must"].is_array());
502 let must = filter["must"].as_array().unwrap();
503 assert_eq!(must.len(), 2);
504
505 assert_eq!(must[0]["key"], "category");
507 assert_eq!(must[0]["match"]["value"], "electronics");
508
509 assert_eq!(must[1]["key"], "price");
511 assert_eq!(must[1]["range"]["lt"], 1000);
512 }
513
514 #[test]
515 fn test_encode_conditions_to_filter_or() {
516 use qail_core::ast::{Condition, Expr, Operator, Value};
517
518 let conditions = vec![
519 Condition {
520 left: Expr::Named("status".to_string()),
521 op: Operator::Eq,
522 value: Value::String("active".to_string()),
523 is_array_unnest: false,
524 },
525 ];
526
527 let filter = encode_conditions_to_filter(&conditions, true);
528
529 assert!(filter["should"].is_array());
531 assert!(filter["must"].is_null());
532 }
533}