1use std::collections::HashMap;
8
9use regex::Regex;
10use serde::{Deserialize, Serialize};
11
12use rouchdb_core::adapter::Adapter;
13use rouchdb_core::collation::collate;
14use rouchdb_core::document::AllDocsOptions;
15use rouchdb_core::error::Result;
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct IndexDefinition {
20 pub name: String,
22 pub fields: Vec<SortField>,
24 #[serde(skip_serializing_if = "Option::is_none")]
26 pub ddoc: Option<String>,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct IndexInfo {
32 pub name: String,
34 pub ddoc: Option<String>,
36 pub def: IndexFields,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct IndexFields {
43 pub fields: Vec<SortField>,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct CreateIndexResponse {
49 pub result: String,
51 pub name: String,
53}
54
55#[derive(Debug, Clone, Serialize)]
57pub struct ExplainResponse {
58 pub dbname: String,
59 pub index: ExplainIndex,
60 pub selector: serde_json::Value,
61 pub fields: Option<Vec<String>>,
62}
63
64#[derive(Debug, Clone, Serialize)]
66pub struct ExplainIndex {
67 pub ddoc: Option<String>,
68 pub name: String,
69 #[serde(rename = "type")]
70 pub index_type: String,
71 pub def: IndexFields,
72}
73
74#[derive(Debug, Clone)]
76pub struct BuiltIndex {
77 pub def: IndexDefinition,
78 pub entries: Vec<(Vec<serde_json::Value>, String)>,
79}
80
81impl BuiltIndex {
82 pub fn find_matching(&self, selector: &serde_json::Value) -> Vec<String> {
84 if self.def.fields.is_empty() {
85 return Vec::new();
86 }
87
88 let (first_field, _) = self.def.fields[0].field_and_direction();
90
91 if let Some(conditions) = selector.get(first_field) {
92 match conditions {
93 serde_json::Value::Object(ops) => {
94 self.entries
96 .iter()
97 .filter(|(key, _)| {
98 if key.is_empty() {
99 return false;
100 }
101 let val = &key[0];
102 for (op, operand) in ops {
103 let matches = match op.as_str() {
104 "$eq" => collate(val, operand) == std::cmp::Ordering::Equal,
105 "$gt" => collate(val, operand) == std::cmp::Ordering::Greater,
106 "$gte" => collate(val, operand) != std::cmp::Ordering::Less,
107 "$lt" => collate(val, operand) == std::cmp::Ordering::Less,
108 "$lte" => collate(val, operand) != std::cmp::Ordering::Greater,
109 _ => true, };
111 if !matches {
112 return false;
113 }
114 }
115 true
116 })
117 .map(|(_, id)| id.clone())
118 .collect()
119 }
120 other => self
122 .entries
123 .iter()
124 .filter(|(key, _)| {
125 !key.is_empty() && collate(&key[0], other) == std::cmp::Ordering::Equal
126 })
127 .map(|(_, id)| id.clone())
128 .collect(),
129 }
130 } else {
131 self.entries.iter().map(|(_, id)| id.clone()).collect()
133 }
134 }
135}
136
137pub async fn build_index(adapter: &dyn Adapter, def: &IndexDefinition) -> Result<BuiltIndex> {
139 let all = adapter
140 .all_docs(AllDocsOptions {
141 include_docs: true,
142 ..AllDocsOptions::new()
143 })
144 .await?;
145
146 let mut entries: Vec<(Vec<serde_json::Value>, String)> = Vec::new();
147
148 for row in &all.rows {
149 if let Some(ref doc_json) = row.doc {
150 let key: Vec<serde_json::Value> = def
151 .fields
152 .iter()
153 .map(|sf| {
154 let (field, _) = sf.field_and_direction();
155 get_nested_field(doc_json, field)
156 .cloned()
157 .unwrap_or(serde_json::Value::Null)
158 })
159 .collect();
160 entries.push((key, row.id.clone()));
161 }
162 }
163
164 entries.sort_by(|(a, _), (b, _)| {
166 for (va, vb) in a.iter().zip(b.iter()) {
167 let cmp = collate(va, vb);
168 if cmp != std::cmp::Ordering::Equal {
169 return cmp;
170 }
171 }
172 std::cmp::Ordering::Equal
173 });
174
175 Ok(BuiltIndex {
176 def: def.clone(),
177 entries,
178 })
179}
180
181#[derive(Debug, Clone, Default, Serialize, Deserialize)]
183pub struct FindOptions {
184 pub selector: serde_json::Value,
186 #[serde(skip_serializing_if = "Option::is_none")]
188 pub fields: Option<Vec<String>>,
189 #[serde(skip_serializing_if = "Option::is_none")]
191 pub sort: Option<Vec<SortField>>,
192 #[serde(skip_serializing_if = "Option::is_none")]
194 pub limit: Option<u64>,
195 #[serde(skip_serializing_if = "Option::is_none")]
197 pub skip: Option<u64>,
198}
199
200#[derive(Debug, Clone, Serialize, Deserialize)]
202#[serde(untagged)]
203pub enum SortField {
204 Simple(String),
206 WithDirection(HashMap<String, String>),
208}
209
210impl SortField {
211 pub fn field_and_direction(&self) -> (&str, SortDirection) {
212 match self {
213 SortField::Simple(f) => (f.as_str(), SortDirection::Asc),
214 SortField::WithDirection(map) => {
215 let (field, dir) = map.iter().next().unwrap();
216 let direction = if dir == "desc" {
217 SortDirection::Desc
218 } else {
219 SortDirection::Asc
220 };
221 (field.as_str(), direction)
222 }
223 }
224 }
225}
226
227#[derive(Debug, Clone, Copy, PartialEq)]
228pub enum SortDirection {
229 Asc,
230 Desc,
231}
232
233#[derive(Debug, Clone, Serialize, Deserialize)]
235pub struct FindResponse {
236 pub docs: Vec<serde_json::Value>,
237}
238
239pub async fn find(adapter: &dyn Adapter, opts: FindOptions) -> Result<FindResponse> {
241 let all = adapter
243 .all_docs(AllDocsOptions {
244 include_docs: true,
245 ..AllDocsOptions::new()
246 })
247 .await?;
248
249 let mut matched: Vec<serde_json::Value> = Vec::new();
250
251 for row in &all.rows {
252 if let Some(ref doc_json) = row.doc
253 && matches_selector(doc_json, &opts.selector)
254 {
255 matched.push(doc_json.clone());
256 }
257 }
258
259 if let Some(ref sort_fields) = opts.sort {
261 matched.sort_by(|a, b| {
262 for sf in sort_fields {
263 let (field, direction) = sf.field_and_direction();
264 let va = get_nested_field(a, field);
265 let vb = get_nested_field(b, field);
266 let va = va.unwrap_or(&serde_json::Value::Null);
267 let vb = vb.unwrap_or(&serde_json::Value::Null);
268 let cmp = collate(va, vb);
269 let cmp = if direction == SortDirection::Desc {
270 cmp.reverse()
271 } else {
272 cmp
273 };
274 if cmp != std::cmp::Ordering::Equal {
275 return cmp;
276 }
277 }
278 std::cmp::Ordering::Equal
279 });
280 }
281
282 if let Some(skip) = opts.skip {
284 matched = matched.into_iter().skip(skip as usize).collect();
285 }
286
287 if let Some(limit) = opts.limit {
289 matched.truncate(limit as usize);
290 }
291
292 if let Some(ref fields) = opts.fields {
294 matched = matched
295 .into_iter()
296 .map(|doc| project(doc, fields))
297 .collect();
298 }
299
300 Ok(FindResponse { docs: matched })
301}
302
303pub fn matches_selector(doc: &serde_json::Value, selector: &serde_json::Value) -> bool {
305 match selector {
306 serde_json::Value::Object(map) => {
307 for (key, condition) in map {
308 if !match_condition(doc, key, condition) {
309 return false;
310 }
311 }
312 true
313 }
314 _ => false,
315 }
316}
317
318fn match_condition(doc: &serde_json::Value, key: &str, condition: &serde_json::Value) -> bool {
319 match key {
321 "$and" => return match_and(doc, condition),
322 "$or" => return match_or(doc, condition),
323 "$not" => return match_not(doc, condition),
324 "$nor" => return match_nor(doc, condition),
325 _ => {}
326 }
327
328 let field_value = get_nested_field(doc, key);
329
330 match condition {
331 serde_json::Value::Object(ops) => {
333 for (op, operand) in ops {
334 if !match_operator(field_value, op, operand) {
335 return false;
336 }
337 }
338 true
339 }
340 other => match_operator(field_value, "$eq", other),
342 }
343}
344
345fn elem_matches(elem: &serde_json::Value, operand: &serde_json::Value) -> bool {
352 if let Some(map) = operand.as_object() {
353 if !map.is_empty() && map.keys().all(|k| k.starts_with('$')) {
354 return map
355 .iter()
356 .all(|(op, sub)| match_operator(Some(elem), op, sub));
357 }
358 return matches_selector(elem, operand);
359 }
360 match_operator(Some(elem), "$eq", operand)
361}
362
363fn match_operator(
364 field_value: Option<&serde_json::Value>,
365 op: &str,
366 operand: &serde_json::Value,
367) -> bool {
368 match op {
369 "$eq" => field_value.is_some_and(|v| collate(v, operand) == std::cmp::Ordering::Equal),
370 "$ne" => field_value.is_none_or(|v| collate(v, operand) != std::cmp::Ordering::Equal),
371 "$gt" => field_value.is_some_and(|v| collate(v, operand) == std::cmp::Ordering::Greater),
372 "$gte" => field_value.is_some_and(|v| collate(v, operand) != std::cmp::Ordering::Less),
373 "$lt" => field_value.is_some_and(|v| collate(v, operand) == std::cmp::Ordering::Less),
374 "$lte" => field_value.is_some_and(|v| collate(v, operand) != std::cmp::Ordering::Greater),
375 "$in" => {
376 if let Some(arr) = operand.as_array() {
377 field_value.is_some_and(|v| {
378 arr.iter()
379 .any(|item| collate(v, item) == std::cmp::Ordering::Equal)
380 })
381 } else {
382 false
383 }
384 }
385 "$nin" => {
386 if let Some(arr) = operand.as_array() {
387 field_value.is_none_or(|v| {
388 !arr.iter()
389 .any(|item| collate(v, item) == std::cmp::Ordering::Equal)
390 })
391 } else {
392 true
393 }
394 }
395 "$exists" => {
396 let should_exist = operand.as_bool().unwrap_or(true);
397 if should_exist {
398 field_value.is_some()
399 } else {
400 field_value.is_none()
401 }
402 }
403 "$type" => {
404 if let Some(type_name) = operand.as_str() {
405 field_value.is_some_and(|v| json_type_name(v) == type_name)
406 } else {
407 false
408 }
409 }
410 "$regex" => {
411 if let Some(pattern) = operand.as_str() {
412 field_value.is_some_and(|v| {
413 if let Some(s) = v.as_str() {
414 Regex::new(pattern).is_ok_and(|re| re.is_match(s))
415 } else {
416 false
417 }
418 })
419 } else {
420 false
421 }
422 }
423 "$size" => {
424 if let Some(expected_size) = operand.as_u64() {
425 field_value.is_some_and(|v| {
426 v.as_array()
427 .is_some_and(|arr| arr.len() as u64 == expected_size)
428 })
429 } else {
430 false
431 }
432 }
433 "$all" => {
434 if let Some(required) = operand.as_array() {
435 field_value.is_some_and(|v| {
436 if let Some(arr) = v.as_array() {
437 required.iter().all(|req| {
438 arr.iter()
439 .any(|item| collate(item, req) == std::cmp::Ordering::Equal)
440 })
441 } else {
442 false
443 }
444 })
445 } else {
446 false
447 }
448 }
449 "$elemMatch" => field_value.is_some_and(|v| {
450 if let Some(arr) = v.as_array() {
451 arr.iter().any(|elem| elem_matches(elem, operand))
452 } else {
453 false
454 }
455 }),
456 "$not" => {
457 if let Some(ops) = operand.as_object() {
459 for (sub_op, sub_operand) in ops {
460 if match_operator(field_value, sub_op, sub_operand) {
461 return false;
462 }
463 }
464 true
465 } else {
466 !match_operator(field_value, "$eq", operand)
468 }
469 }
470 "$mod" => {
471 if let Some(arr) = operand.as_array() {
472 if arr.len() == 2 {
473 let divisor = arr[0].as_i64();
474 let remainder = arr[1].as_i64();
475 if let (Some(d), Some(r)) = (divisor, remainder) {
476 field_value
477 .is_some_and(|v| v.as_i64().is_some_and(|n| d != 0 && n % d == r))
478 } else {
479 false
480 }
481 } else {
482 false
483 }
484 } else {
485 false
486 }
487 }
488 _ => false,
489 }
490}
491
492fn match_and(doc: &serde_json::Value, condition: &serde_json::Value) -> bool {
493 if let Some(arr) = condition.as_array() {
494 arr.iter().all(|sub| matches_selector(doc, sub))
495 } else {
496 false
497 }
498}
499
500fn match_or(doc: &serde_json::Value, condition: &serde_json::Value) -> bool {
501 if let Some(arr) = condition.as_array() {
502 arr.iter().any(|sub| matches_selector(doc, sub))
503 } else {
504 false
505 }
506}
507
508fn match_not(doc: &serde_json::Value, condition: &serde_json::Value) -> bool {
509 !matches_selector(doc, condition)
510}
511
512fn match_nor(doc: &serde_json::Value, condition: &serde_json::Value) -> bool {
513 if let Some(arr) = condition.as_array() {
514 !arr.iter().any(|sub| matches_selector(doc, sub))
515 } else {
516 false
517 }
518}
519
520pub fn get_nested_field<'a>(
522 doc: &'a serde_json::Value,
523 path: &str,
524) -> Option<&'a serde_json::Value> {
525 let mut current = doc;
526 for part in path.split('.') {
527 match current.get(part) {
528 Some(v) => current = v,
529 None => return None,
530 }
531 }
532 Some(current)
533}
534
535fn json_type_name(value: &serde_json::Value) -> &'static str {
537 match value {
538 serde_json::Value::Null => "null",
539 serde_json::Value::Bool(_) => "boolean",
540 serde_json::Value::Number(_) => "number",
541 serde_json::Value::String(_) => "string",
542 serde_json::Value::Array(_) => "array",
543 serde_json::Value::Object(_) => "object",
544 }
545}
546
547fn project(doc: serde_json::Value, fields: &[String]) -> serde_json::Value {
549 let mut result = serde_json::Map::new();
550
551 if let serde_json::Value::Object(map) = &doc {
552 for field in fields {
553 if let Some(val) = map.get(field) {
555 result.insert(field.clone(), val.clone());
556 }
557 }
558 if let Some(id) = map.get("_id") {
560 result
561 .entry("_id".to_string())
562 .or_insert_with(|| id.clone());
563 }
564 }
565
566 serde_json::Value::Object(result)
567}
568
569#[cfg(test)]
574mod tests {
575 use super::*;
576
577 fn doc(json: serde_json::Value) -> serde_json::Value {
578 json
579 }
580
581 #[test]
582 fn elem_match_operator_on_scalar_array() {
583 let d = doc(serde_json::json!({"scores": [50, 85, 60]}));
584 assert!(matches_selector(
586 &d,
587 &serde_json::json!({"scores": {"$elemMatch": {"$gt": 80}}})
588 ));
589 assert!(!matches_selector(
590 &d,
591 &serde_json::json!({"scores": {"$elemMatch": {"$gt": 90}}})
592 ));
593 assert!(matches_selector(
595 &d,
596 &serde_json::json!({"scores": {"$elemMatch": 85}})
597 ));
598 }
599
600 #[test]
601 fn elem_match_subdocument_array() {
602 let d = doc(serde_json::json!({
603 "items": [{"subject": "math", "score": 90}, {"subject": "art", "score": 70}]
604 }));
605 assert!(matches_selector(
606 &d,
607 &serde_json::json!({"items": {"$elemMatch": {"subject": "math"}}})
608 ));
609 assert!(!matches_selector(
610 &d,
611 &serde_json::json!({"items": {"$elemMatch": {"subject": "history"}}})
612 ));
613 }
614
615 #[test]
618 fn eq_implicit() {
619 let d = doc(serde_json::json!({"name": "Alice", "age": 30}));
620 assert!(matches_selector(&d, &serde_json::json!({"name": "Alice"})));
621 assert!(!matches_selector(&d, &serde_json::json!({"name": "Bob"})));
622 }
623
624 #[test]
625 fn eq_explicit() {
626 let d = doc(serde_json::json!({"age": 30}));
627 assert!(matches_selector(
628 &d,
629 &serde_json::json!({"age": {"$eq": 30}})
630 ));
631 }
632
633 #[test]
634 fn ne() {
635 let d = doc(serde_json::json!({"age": 30}));
636 assert!(matches_selector(
637 &d,
638 &serde_json::json!({"age": {"$ne": 25}})
639 ));
640 assert!(!matches_selector(
641 &d,
642 &serde_json::json!({"age": {"$ne": 30}})
643 ));
644 }
645
646 #[test]
647 fn gt_gte_lt_lte() {
648 let d = doc(serde_json::json!({"age": 30}));
649
650 assert!(matches_selector(
651 &d,
652 &serde_json::json!({"age": {"$gt": 20}})
653 ));
654 assert!(!matches_selector(
655 &d,
656 &serde_json::json!({"age": {"$gt": 30}})
657 ));
658
659 assert!(matches_selector(
660 &d,
661 &serde_json::json!({"age": {"$gte": 30}})
662 ));
663 assert!(!matches_selector(
664 &d,
665 &serde_json::json!({"age": {"$gte": 31}})
666 ));
667
668 assert!(matches_selector(
669 &d,
670 &serde_json::json!({"age": {"$lt": 40}})
671 ));
672 assert!(!matches_selector(
673 &d,
674 &serde_json::json!({"age": {"$lt": 30}})
675 ));
676
677 assert!(matches_selector(
678 &d,
679 &serde_json::json!({"age": {"$lte": 30}})
680 ));
681 assert!(!matches_selector(
682 &d,
683 &serde_json::json!({"age": {"$lte": 29}})
684 ));
685 }
686
687 #[test]
688 fn in_nin() {
689 let d = doc(serde_json::json!({"color": "red"}));
690
691 assert!(matches_selector(
692 &d,
693 &serde_json::json!({"color": {"$in": ["red", "blue"]}})
694 ));
695 assert!(!matches_selector(
696 &d,
697 &serde_json::json!({"color": {"$in": ["green", "blue"]}})
698 ));
699
700 assert!(matches_selector(
701 &d,
702 &serde_json::json!({"color": {"$nin": ["green", "blue"]}})
703 ));
704 assert!(!matches_selector(
705 &d,
706 &serde_json::json!({"color": {"$nin": ["red", "blue"]}})
707 ));
708 }
709
710 #[test]
711 fn exists() {
712 let d = doc(serde_json::json!({"name": "Alice"}));
713
714 assert!(matches_selector(
715 &d,
716 &serde_json::json!({"name": {"$exists": true}})
717 ));
718 assert!(!matches_selector(
719 &d,
720 &serde_json::json!({"age": {"$exists": true}})
721 ));
722 assert!(matches_selector(
723 &d,
724 &serde_json::json!({"age": {"$exists": false}})
725 ));
726 }
727
728 #[test]
729 fn type_check() {
730 let d = doc(serde_json::json!({"name": "Alice", "age": 30, "active": true}));
731
732 assert!(matches_selector(
733 &d,
734 &serde_json::json!({"name": {"$type": "string"}})
735 ));
736 assert!(matches_selector(
737 &d,
738 &serde_json::json!({"age": {"$type": "number"}})
739 ));
740 assert!(matches_selector(
741 &d,
742 &serde_json::json!({"active": {"$type": "boolean"}})
743 ));
744 }
745
746 #[test]
747 fn regex_match() {
748 let d = doc(serde_json::json!({"name": "Alice"}));
749
750 assert!(matches_selector(
751 &d,
752 &serde_json::json!({"name": {"$regex": "^Ali"}})
753 ));
754 assert!(!matches_selector(
755 &d,
756 &serde_json::json!({"name": {"$regex": "^Bob"}})
757 ));
758 }
759
760 #[test]
761 fn size_operator() {
762 let d = doc(serde_json::json!({"tags": ["a", "b", "c"]}));
763
764 assert!(matches_selector(
765 &d,
766 &serde_json::json!({"tags": {"$size": 3}})
767 ));
768 assert!(!matches_selector(
769 &d,
770 &serde_json::json!({"tags": {"$size": 2}})
771 ));
772 }
773
774 #[test]
775 fn all_operator() {
776 let d = doc(serde_json::json!({"tags": ["a", "b", "c"]}));
777
778 assert!(matches_selector(
779 &d,
780 &serde_json::json!({"tags": {"$all": ["a", "c"]}})
781 ));
782 assert!(!matches_selector(
783 &d,
784 &serde_json::json!({"tags": {"$all": ["a", "d"]}})
785 ));
786 }
787
788 #[test]
789 fn elem_match() {
790 let d = doc(serde_json::json!({
791 "scores": [
792 {"subject": "math", "grade": 90},
793 {"subject": "english", "grade": 75}
794 ]
795 }));
796
797 assert!(matches_selector(
798 &d,
799 &serde_json::json!({"scores": {"$elemMatch": {"subject": "math", "grade": {"$gt": 80}}}})
800 ));
801 assert!(!matches_selector(
802 &d,
803 &serde_json::json!({"scores": {"$elemMatch": {"subject": "math", "grade": {"$gt": 95}}}})
804 ));
805 }
806
807 #[test]
808 fn mod_operator() {
809 let d = doc(serde_json::json!({"n": 10}));
810
811 assert!(matches_selector(
812 &d,
813 &serde_json::json!({"n": {"$mod": [3, 1]}})
814 ));
815 assert!(!matches_selector(
816 &d,
817 &serde_json::json!({"n": {"$mod": [3, 0]}})
818 ));
819 }
820
821 #[test]
824 fn and_operator() {
825 let d = doc(serde_json::json!({"age": 30, "active": true}));
826
827 assert!(matches_selector(
828 &d,
829 &serde_json::json!({"$and": [{"age": {"$gte": 20}}, {"active": true}]})
830 ));
831 assert!(!matches_selector(
832 &d,
833 &serde_json::json!({"$and": [{"age": {"$gte": 20}}, {"active": false}]})
834 ));
835 }
836
837 #[test]
838 fn or_operator() {
839 let d = doc(serde_json::json!({"age": 30}));
840
841 assert!(matches_selector(
842 &d,
843 &serde_json::json!({"$or": [{"age": 30}, {"age": 40}]})
844 ));
845 assert!(!matches_selector(
846 &d,
847 &serde_json::json!({"$or": [{"age": 20}, {"age": 40}]})
848 ));
849 }
850
851 #[test]
852 fn not_operator() {
853 let d = doc(serde_json::json!({"age": 30}));
854
855 assert!(matches_selector(
856 &d,
857 &serde_json::json!({"$not": {"age": 40}})
858 ));
859 assert!(!matches_selector(
860 &d,
861 &serde_json::json!({"$not": {"age": 30}})
862 ));
863 }
864
865 #[test]
866 fn nor_operator() {
867 let d = doc(serde_json::json!({"age": 30}));
868
869 assert!(matches_selector(
870 &d,
871 &serde_json::json!({"$nor": [{"age": 20}, {"age": 40}]})
872 ));
873 assert!(!matches_selector(
874 &d,
875 &serde_json::json!({"$nor": [{"age": 30}, {"age": 40}]})
876 ));
877 }
878
879 #[test]
882 fn nested_field_access() {
883 let d = doc(serde_json::json!({"address": {"city": "NYC", "zip": "10001"}}));
884
885 assert!(matches_selector(
886 &d,
887 &serde_json::json!({"address.city": "NYC"})
888 ));
889 assert!(!matches_selector(
890 &d,
891 &serde_json::json!({"address.city": "LA"})
892 ));
893 }
894
895 #[test]
898 fn multiple_field_conditions() {
899 let d = doc(serde_json::json!({"name": "Alice", "age": 30}));
900
901 assert!(matches_selector(
903 &d,
904 &serde_json::json!({"name": "Alice", "age": {"$gte": 25}})
905 ));
906 assert!(!matches_selector(
907 &d,
908 &serde_json::json!({"name": "Alice", "age": {"$gte": 35}})
909 ));
910 }
911
912 #[test]
913 fn combined_operators_on_field() {
914 let d = doc(serde_json::json!({"age": 30}));
915
916 assert!(matches_selector(
918 &d,
919 &serde_json::json!({"age": {"$gt": 20, "$lt": 40}})
920 ));
921 assert!(!matches_selector(
922 &d,
923 &serde_json::json!({"age": {"$gt": 30, "$lt": 40}})
924 ));
925 }
926
927 #[test]
930 fn project_fields() {
931 let d = serde_json::json!({"_id": "doc1", "_rev": "1-abc", "name": "Alice", "age": 30});
932 let projected = project(d, &["name".to_string()]);
933
934 assert_eq!(projected["_id"], "doc1");
935 assert_eq!(projected["name"], "Alice");
936 assert!(projected.get("age").is_none());
937 }
938
939 #[test]
942 fn missing_field_ne_matches() {
943 let d = doc(serde_json::json!({"name": "Alice"}));
945 assert!(matches_selector(
946 &d,
947 &serde_json::json!({"age": {"$ne": 30}})
948 ));
949 }
950
951 #[test]
952 fn missing_field_eq_fails() {
953 let d = doc(serde_json::json!({"name": "Alice"}));
954 assert!(!matches_selector(
955 &d,
956 &serde_json::json!({"age": {"$eq": 30}})
957 ));
958 }
959}