use std::cmp::Ordering;
use serde_json::{Map, Value};
use crate::bson;
use super::pipeline::{float_of, number_of};
pub const MAX_LOCAL_COLLECTION_DOCS: usize = 100_000;
const RANK_MINKEY: u8 = 1;
const RANK_NULL: u8 = 2;
const RANK_NUMBER: u8 = 3;
const RANK_STRING: u8 = 4;
const RANK_OBJECT: u8 = 5;
const RANK_ARRAY: u8 = 6;
const RANK_BINARY: u8 = 7;
const RANK_OID: u8 = 8;
const RANK_BOOL: u8 = 9;
const RANK_DATE: u8 = 10;
const RANK_TIMESTAMP: u8 = 11;
const RANK_REGEX: u8 = 12;
const RANK_MAXKEY: u8 = 13;
fn segments(path: &str) -> Vec<&str> {
if path.is_empty() {
Vec::new()
} else {
path.split('.').collect()
}
}
pub fn get_path<'a>(doc: &'a Value, path: &str) -> Option<&'a Value> {
let mut cur = doc;
for seg in segments(path) {
cur = match cur {
Value::Object(o) => o.get(seg)?,
_ => return None,
};
}
Some(cur)
}
pub fn resolve_path_candidates<'a>(doc: &'a Value, path: &str) -> Vec<&'a Value> {
let mut out = Vec::new();
collect_candidates(doc, &segments(path), &mut out);
out
}
fn collect_candidates<'a>(v: &'a Value, segs: &[&str], out: &mut Vec<&'a Value>) {
if segs.is_empty() {
out.push(v);
return;
}
match v {
Value::Object(o) => {
if let Some(next) = o.get(segs[0]) {
collect_candidates(next, &segs[1..], out);
}
}
Value::Array(a) => {
for el in a {
collect_candidates(el, segs, out);
}
}
_ => {}
}
}
pub fn bson_rank(v: &Value) -> u8 {
match v {
Value::Null => RANK_NULL,
Value::Bool(_) => RANK_BOOL,
Value::Number(_) => RANK_NUMBER,
Value::String(_) => RANK_STRING,
Value::Array(_) => RANK_ARRAY,
Value::Object(o) => extended_kind(o).unwrap_or(RANK_OBJECT),
}
}
fn extended_kind(o: &Map<String, Value>) -> Option<u8> {
if o.len() != 1 {
return None;
}
match o.keys().next()?.as_str() {
"$minKey" => Some(RANK_MINKEY),
"$maxKey" => Some(RANK_MAXKEY),
"$numberDecimal" | "$numberLong" => Some(RANK_NUMBER),
"$oid" => Some(RANK_OID),
"$date" => Some(RANK_DATE),
"$timestamp" => Some(RANK_TIMESTAMP),
"$binary" => Some(RANK_BINARY),
"$regularExpression" => Some(RANK_REGEX),
_ => None,
}
}
pub fn compare(a: &Value, b: &Value) -> Ordering {
let (ra, rb) = (bson_rank(a), bson_rank(b));
if ra != rb {
return ra.cmp(&rb);
}
match ra {
RANK_NUMBER => compare_numbers(a, b),
RANK_STRING => a.as_str().unwrap_or("").cmp(b.as_str().unwrap_or("")),
RANK_BOOL => bool_rank(a).cmp(&bool_rank(b)),
RANK_OBJECT => compare_objects(
a.as_object().expect("rank=object"),
b.as_object().expect("rank=object"),
),
RANK_ARRAY => compare_arrays(
a.as_array().expect("rank=array"),
b.as_array().expect("rank=array"),
),
RANK_OID => oid_str(a).cmp(oid_str(b)),
RANK_DATE => date_ms(a).cmp(&date_ms(b)),
RANK_TIMESTAMP => ts_parts(a).cmp(&ts_parts(b)),
RANK_BINARY => compare_binary(a, b),
RANK_REGEX => regex_parts(a).cmp(®ex_parts(b)),
_ => Ordering::Equal,
}
}
pub fn values_equal(a: &Value, b: &Value) -> bool {
compare(a, b) == Ordering::Equal
}
pub fn matches_value(actual: &Value, expected: &Value) -> bool {
if values_equal(actual, expected) {
return true;
}
match actual {
Value::Array(a) => a.iter().any(|el| values_equal(el, expected)),
_ => false,
}
}
fn compare_numbers(a: &Value, b: &Value) -> Ordering {
numeric_value(a)
.partial_cmp(&numeric_value(b))
.unwrap_or(Ordering::Equal)
}
pub fn as_number(v: &Value) -> Option<f64> {
match v {
Value::Number(n) => n.as_f64(),
Value::Object(o) => {
if let Some(s) = o.get("$numberLong").and_then(Value::as_str) {
s.parse::<f64>().ok()
} else {
o.get("$numberDecimal")
.and_then(Value::as_str)
.and_then(|s| s.parse::<f64>().ok())
}
}
_ => None,
}
}
fn numeric_value(v: &Value) -> f64 {
as_number(v).unwrap_or(0.0)
}
fn bool_rank(v: &Value) -> u8 {
u8::from(v.as_bool().unwrap_or(false))
}
fn compare_objects(a: &Map<String, Value>, b: &Map<String, Value>) -> Ordering {
let mut ai = a.iter();
let mut bi = b.iter();
loop {
match (ai.next(), bi.next()) {
(None, None) => return Ordering::Equal,
(None, Some(_)) => return Ordering::Less,
(Some(_), None) => return Ordering::Greater,
(Some((ka, va)), Some((kb, vb))) => {
let key_cmp = ka.cmp(kb);
if key_cmp != Ordering::Equal {
return key_cmp;
}
let val_cmp = compare(va, vb);
if val_cmp != Ordering::Equal {
return val_cmp;
}
}
}
}
}
fn compare_arrays(a: &[Value], b: &[Value]) -> Ordering {
for (x, y) in a.iter().zip(b.iter()) {
let c = compare(x, y);
if c != Ordering::Equal {
return c;
}
}
a.len().cmp(&b.len())
}
fn oid_str(v: &Value) -> &str {
v.get("$oid").and_then(Value::as_str).unwrap_or("")
}
fn date_ms(v: &Value) -> i64 {
v.get("$date").and_then(Value::as_i64).unwrap_or(0)
}
fn ts_parts(v: &Value) -> (i64, i64) {
match v.get("$timestamp").and_then(Value::as_object) {
Some(o) => (
o.get("t").and_then(Value::as_i64).unwrap_or(0),
o.get("i").and_then(Value::as_i64).unwrap_or(0),
),
None => (0, 0),
}
}
fn compare_binary(a: &Value, b: &Value) -> Ordering {
let (a_b64, a_sub) = binary_parts(a);
let (b_b64, b_sub) = binary_parts(b);
a_b64
.len()
.cmp(&b_b64.len())
.then_with(|| a_sub.cmp(&b_sub))
.then_with(|| a_b64.cmp(&b_b64))
}
fn binary_parts(v: &Value) -> (String, String) {
match v.get("$binary").and_then(Value::as_object) {
Some(o) => (
o.get("base64")
.and_then(Value::as_str)
.unwrap_or("")
.to_string(),
o.get("subType")
.and_then(Value::as_str)
.unwrap_or("")
.to_string(),
),
None => (String::new(), String::new()),
}
}
fn regex_parts(v: &Value) -> (String, String) {
match v.get("$regularExpression").and_then(Value::as_object) {
Some(o) => (
o.get("pattern")
.and_then(Value::as_str)
.unwrap_or("")
.to_string(),
o.get("options")
.and_then(Value::as_str)
.unwrap_or("")
.to_string(),
),
None => (String::new(), String::new()),
}
}
pub fn eval_expr(expr: &Value, doc: &Value, vars: &Map<String, Value>) -> Result<Value, String> {
match expr {
Value::String(s) => eval_ref(s, doc, vars),
Value::Array(a) => a
.iter()
.map(|e| eval_expr(e, doc, vars))
.collect::<Result<Vec<_>, _>>()
.map(Value::Array),
Value::Object(o) => {
if bson::is_extended(expr) {
return Ok(expr.clone());
}
if o.len() == 1 {
if let Some(op) = o.keys().next().map(String::as_str) {
if let Some(stripped) = op.strip_prefix('$') {
return eval_operator(
stripped,
o.values().next().expect("len=1"),
doc,
vars,
);
}
}
}
let mut out = Map::new();
for (k, v) in o {
out.insert(k.clone(), eval_expr(v, doc, vars)?);
}
Ok(Value::Object(out))
}
other => Ok(other.clone()),
}
}
fn eval_operator(
op: &str,
arg: &Value,
doc: &Value,
vars: &Map<String, Value>,
) -> Result<Value, String> {
match op {
"eq" => {
let (a, b) = two_operands("$eq", arg)?;
Ok(Value::Bool(values_equal(
&eval_expr(a, doc, vars)?,
&eval_expr(b, doc, vars)?,
)))
}
"and" => {
for e in array_operands("$and", arg)? {
if !truthy(&eval_expr(e, doc, vars)?) {
return Ok(Value::Bool(false));
}
}
Ok(Value::Bool(true))
}
"or" => {
for e in array_operands("$or", arg)? {
if truthy(&eval_expr(e, doc, vars)?) {
return Ok(Value::Bool(true));
}
}
Ok(Value::Bool(false))
}
"ifNull" => {
let (a, b) = two_operands("$ifNull", arg)?;
let v = eval_expr(a, doc, vars)?;
if v.is_null() {
eval_expr(b, doc, vars)
} else {
Ok(v)
}
}
"cond" => {
let (c, t, e) = cond_operands(arg)?;
if truthy(&eval_expr(c, doc, vars)?) {
eval_expr(t, doc, vars)
} else {
eval_expr(e, doc, vars)
}
}
"isArray" => Ok(Value::Bool(eval_expr(arg, doc, vars)?.is_array())),
"in" => {
let (needle, hay) = two_operands("$in", arg)?;
let needle = eval_expr(needle, doc, vars)?;
let hay = eval_expr(hay, doc, vars)?;
let items = hay
.as_array()
.ok_or_else(|| "$in 的第二个操作数必须是数组".to_string())?;
let hit = match &needle {
Value::Array(a) => a
.iter()
.any(|n| items.iter().any(|c| values_equal(n, c))),
other => items.iter().any(|c| values_equal(other, c)),
};
Ok(Value::Bool(hit))
}
"arrayElemAt" => {
let (arr_e, idx_e) = two_operands("$arrayElemAt", arg)?;
let arr = eval_expr(arr_e, doc, vars)?;
let idx_v = eval_expr(idx_e, doc, vars)?;
let idx = idx_v
.as_i64()
.ok_or_else(|| "$arrayElemAt 的下标必须是整数".to_string())?;
let items = arr
.as_array()
.ok_or_else(|| "$arrayElemAt 的第一个操作数必须是数组".to_string())?;
let len = items.len() as i64;
let i = if idx < 0 { len + idx } else { idx };
Ok(items.get(i as usize).cloned().unwrap_or(Value::Null))
}
"size" => {
let arr = eval_expr(arg, doc, vars)?;
let items = arr
.as_array()
.ok_or_else(|| format!("$size 的操作数必须是数组,收到 {}", value_type_name(&arr)))?;
Ok(Value::from(items.len() as i64))
}
"sum" | "avg" | "min" | "max" => {
let v = eval_expr(arg, doc, vars)?;
match v {
Value::Array(items) => accumulate_expr(op, &items),
other => accumulate_expr(op, std::slice::from_ref(&other)),
}
}
other => Err(format!(
"不支持的聚合表达式算子: ${other}(本地求值器仅支持 $eq/$and/$or/$ifNull/$cond/$isArray/$in/$arrayElemAt/$size/$sum/$avg/$min/$max,拒绝静默)"
)),
}
}
fn accumulate_expr(op: &str, items: &[Value]) -> Result<Value, String> {
match op {
"sum" => {
let mut sum = 0.0_f64;
for v in items {
if let Some(n) = as_number(v) {
sum += n;
}
}
Ok(number_of(sum))
}
"avg" => {
let (mut sum, mut count) = (0.0_f64, 0_usize);
for v in items {
if let Some(n) = as_number(v) {
sum += n;
count += 1;
}
}
Ok(if count == 0 {
Value::Null
} else {
float_of(sum / count as f64)
})
}
_ => {
let mut best: Option<&Value> = None;
for v in items {
if v.is_null() {
continue;
}
best = Some(match best {
None => v,
Some(cur) => {
let take = if op == "min" {
compare(v, cur) == Ordering::Less
} else {
compare(v, cur) == Ordering::Greater
};
if take {
v
} else {
cur
}
}
});
}
Ok(best.cloned().unwrap_or(Value::Null))
}
}
}
fn value_type_name(v: &Value) -> &'static str {
match v {
Value::Null => "null",
Value::Bool(_) => "bool",
Value::Number(_) => "number",
Value::String(_) => "string",
Value::Array(_) => "array",
Value::Object(_) => "object",
}
}
fn two_operands<'a>(name: &str, arg: &'a Value) -> Result<(&'a Value, &'a Value), String> {
let arr = arg
.as_array()
.ok_or_else(|| format!("{name} 需要操作数数组"))?;
if arr.len() != 2 {
return Err(format!("{name} 需要恰好两个操作数,收到 {}", arr.len()));
}
Ok((&arr[0], &arr[1]))
}
fn array_operands<'a>(name: &str, arg: &'a Value) -> Result<&'a [Value], String> {
arg.as_array()
.map(Vec::as_slice)
.ok_or_else(|| format!("{name} 需要操作数数组"))
}
fn cond_operands(arg: &Value) -> Result<(&Value, &Value, &Value), String> {
if let Some(a) = arg.as_array() {
if a.len() != 3 {
return Err(format!("$cond 数组形态需要 3 个操作数,收到 {}", a.len()));
}
return Ok((&a[0], &a[1], &a[2]));
}
if let Some(o) = arg.as_object() {
let pick = |k: &str| {
o.get(k)
.ok_or_else(|| format!("$cond 对象形态缺少 \"{k}\""))
};
return Ok((pick("if")?, pick("then")?, pick("else")?));
}
Err("$cond 需要数组 [if, then, else] 或对象 {if, then, else}".to_string())
}
fn truthy(v: &Value) -> bool {
match v {
Value::Null => false,
Value::Bool(b) => *b,
Value::Number(n) => n.as_f64().map(|x| x != 0.0).unwrap_or(true),
_ => true,
}
}
fn eval_ref(s: &str, doc: &Value, vars: &Map<String, Value>) -> Result<Value, String> {
if let Some(rest) = s.strip_prefix("$$") {
let (name, path) = match rest.split_once('.') {
Some((n, p)) => (n, p),
None => (rest, ""),
};
let base = vars.get(name).cloned().unwrap_or(Value::Null);
Ok(reference_path(&base, path))
} else if let Some(rest) = s.strip_prefix('$') {
Ok(get_path(doc, rest).cloned().unwrap_or(Value::Null))
} else {
Ok(Value::String(s.to_string()))
}
}
fn reference_path(base: &Value, path: &str) -> Value {
if path.is_empty() {
base.clone()
} else {
get_path(base, path).cloned().unwrap_or(Value::Null)
}
}
pub fn apply_projection(doc: &Value, projection: &Value) -> Result<Value, String> {
let obj = projection
.as_object()
.ok_or_else(|| format!("投影必须是对象,收到 {}", type_name(projection)))?;
if obj.is_empty() {
return Ok(doc.clone());
}
let mut include_paths: Vec<&String> = Vec::new();
let mut exclude_paths: Vec<&String> = Vec::new();
let mut id: Option<bool> = None;
for (k, v) in obj {
let flag = projection_flag(v)?;
if k == "_id" {
id = Some(flag);
} else if flag {
include_paths.push(k);
} else {
exclude_paths.push(k);
}
}
if !include_paths.is_empty() && !exclude_paths.is_empty() {
return Err("投影不允许同时包含与排除字段(`_id` 除外)".to_string());
}
let include_mode = if !include_paths.is_empty() {
true
} else if !exclude_paths.is_empty() {
false
} else {
id == Some(true)
};
if include_mode {
let mut out = Map::new();
if id != Some(false) {
if let Some(v) = doc.get("_id") {
out.insert("_id".to_string(), v.clone());
}
}
for path in &include_paths {
if let Some(v) = project_included(doc, &segments(path)) {
merge_into(&mut out, v);
}
}
Ok(Value::Object(out))
} else {
let mut out = doc.clone();
for path in &exclude_paths {
remove_segments(&mut out, &segments(path));
}
if id == Some(false) {
if let Value::Object(m) = &mut out {
m.remove("_id");
}
}
Ok(out)
}
}
fn projection_flag(v: &Value) -> Result<bool, String> {
match v {
Value::Bool(b) => Ok(*b),
Value::Number(n) => match (n.as_i64(), n.as_f64()) {
(Some(0), _) | (_, Some(0.0)) => Ok(false),
(Some(1), _) | (_, Some(1.0)) => Ok(true),
_ => Err(format!("投影值必须是 0/1 或 false/true,收到 {n}")),
},
other => Err(format!(
"不支持的投影形态: {other}(本地求值器仅支持包含/排除,拒绝静默)"
)),
}
}
fn project_included(doc: &Value, segs: &[&str]) -> Option<Value> {
if segs.is_empty() {
return Some(doc.clone());
}
match doc {
Value::Object(o) => {
let child = o.get(segs[0])?;
let sub = project_included(child, &segs[1..])?;
let mut m = Map::new();
m.insert(segs[0].to_string(), sub);
Some(Value::Object(m))
}
Value::Array(a) => {
let mut arr = Vec::with_capacity(a.len());
for el in a {
if let Some(v) = project_included(el, segs) {
arr.push(v);
}
}
Some(Value::Array(arr))
}
_ => None,
}
}
pub fn project_include(doc: &Value, path: &str) -> Option<Value> {
project_included(doc, &segments(path))
}
pub fn merge_into(dst: &mut Map<String, Value>, src: Value) {
let Value::Object(src) = src else {
return;
};
for (k, v) in src {
let nested = matches!(dst.get(&k), Some(Value::Object(_))) && v.is_object();
if nested {
if let Some(Value::Object(d)) = dst.get_mut(&k) {
merge_into(d, v);
}
} else {
dst.insert(k, v);
}
}
}
pub fn remove_path(doc: &mut Value, path: &str) {
remove_segments(doc, &segments(path));
}
pub fn set_path(doc: &mut Value, path: &str, val: Value) {
set_segments(doc, &segments(path), val);
}
fn set_segments(cur: &mut Value, segs: &[&str], val: Value) {
if segs.is_empty() {
*cur = val;
return;
}
if !cur.is_object() {
*cur = Value::Object(Map::new());
}
let Value::Object(o) = cur else { return };
if segs.len() == 1 {
o.insert(segs[0].to_string(), val);
return;
}
let entry = o
.entry(segs[0].to_string())
.or_insert_with(|| Value::Object(Map::new()));
set_segments(entry, &segs[1..], val);
}
fn remove_segments(doc: &mut Value, segs: &[&str]) {
if segs.is_empty() {
return;
}
match doc {
Value::Object(o) => {
if segs.len() == 1 {
o.remove(segs[0]);
} else if let Some(v) = o.get_mut(segs[0]) {
remove_segments(v, &segs[1..]);
}
}
Value::Array(a) => {
for el in a {
remove_segments(el, segs);
}
}
_ => {}
}
}
fn type_name(v: &Value) -> &'static str {
match v {
Value::Null => "null",
Value::Bool(_) => "bool",
Value::Number(_) => "number",
Value::String(_) => "string",
Value::Array(_) => "array",
Value::Object(_) => "object",
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn max_docs_guard_constant() {
assert_eq!(MAX_LOCAL_COLLECTION_DOCS, 100_000);
}
#[test]
fn get_path_three_states() {
let doc = json!({ "a": null, "b": { "c": 1 } });
assert_eq!(get_path(&doc, "a"), Some(&Value::Null));
assert_eq!(get_path(&doc, "missing"), None);
assert_eq!(get_path(&doc, "b.missing"), None);
assert_eq!(get_path(&doc, "b.c"), Some(&json!(1)));
let arr = json!({ "a": [ { "b": 1 } ] });
assert_eq!(get_path(&arr, "a.b"), None);
}
#[test]
fn candidates_three_states_and_array_expansion() {
let doc = json!({ "a": null, "arr": [ { "b": 1 }, { "b": 2 } ] });
assert!(resolve_path_candidates(&doc, "missing").is_empty());
assert_eq!(resolve_path_candidates(&doc, "a"), vec![&Value::Null]);
assert_eq!(
resolve_path_candidates(&doc, "arr.b"),
vec![&json!(1), &json!(2)]
);
let tags = json!({ "tags": ["x", "y"] });
assert_eq!(
resolve_path_candidates(&tags, "tags"),
vec![&json!(["x", "y"])]
);
}
#[test]
fn bson_type_order_across_types() {
let min = json!({ "$minKey": 1 });
let null = Value::Null;
let num = json!(5);
let s = json!("abc");
let obj = json!({ "x": 1 });
let arr = json!([1, 2]);
let boolean = json!(true);
let date = json!({ "$date": 0 });
let max = json!({ "$maxKey": 1 });
let seq = [&min, &null, &num, &s, &obj, &arr, &boolean, &date, &max];
for (i, a) in seq.iter().enumerate() {
for (j, b) in seq.iter().enumerate() {
assert_eq!(compare(a, b), i.cmp(&j), "rank {i} vs {j}");
}
}
}
#[test]
fn number_order_across_int_and_float() {
assert_eq!(compare(&json!(1), &json!(1.0)), Ordering::Equal);
assert!(values_equal(&json!(1), &json!(1.0)));
assert_eq!(compare(&json!(2), &json!(1.5)), Ordering::Greater);
assert_eq!(compare(&json!(-1), &json!(0)), Ordering::Less);
assert!(values_equal(&json!({ "$numberLong": "3" }), &json!(3)));
}
#[test]
fn compare_object_and_array() {
assert_eq!(
compare(&json!({ "a": 1 }), &json!({ "a": 2 })),
Ordering::Less
);
assert_eq!(
compare(&json!({ "a": 2 }), &json!({ "a": 2, "b": 1 })),
Ordering::Less
);
assert_eq!(compare(&json!([1, 2]), &json!([1, 2, 3])), Ordering::Less);
assert_eq!(compare(&json!([1, 3]), &json!([1, 2])), Ordering::Greater);
}
#[test]
fn array_eq_hit_helper() {
assert!(matches_value(&json!(["x", "y"]), &json!("x")));
assert!(matches_value(&json!("x"), &json!("x")));
assert!(!matches_value(&json!(["y"]), &json!("x")));
}
#[test]
fn eval_expr_minimal() {
let doc = json!({ "x": 1, "y": 2 });
let vars = Map::new();
assert_eq!(
eval_expr(&json!({ "$eq": ["$x", 1] }), &doc, &vars).unwrap(),
json!(true)
);
assert_eq!(
eval_expr(&json!({ "$eq": ["$x", "$y"] }), &doc, &vars).unwrap(),
json!(false)
);
assert_eq!(
eval_expr(
&json!({ "$and": [ { "$eq": ["$x", 1] }, { "$eq": ["$y", 2] } ] }),
&doc,
&vars
)
.unwrap(),
json!(true)
);
assert_eq!(
eval_expr(&json!({ "$ifNull": ["$missing", "d"] }), &doc, &vars).unwrap(),
json!("d")
);
let mut vars2 = Map::new();
vars2.insert("v".to_string(), json!(42));
assert_eq!(eval_expr(&json!("$$v"), &doc, &vars2).unwrap(), json!(42));
assert_eq!(
eval_expr(&json!({ "$eq": ["$x", "$$v"] }), &doc, &vars2).unwrap(),
json!(false)
);
assert!(eval_expr(&json!({ "$mul": [1, 2] }), &doc, &vars).is_err());
}
#[test]
fn expr_size() {
let vars = Map::new();
let doc = json!({ "arr": [1, 2, 3], "empty": [], "n": 5 });
assert_eq!(
eval_expr(&json!({ "$size": "$arr" }), &doc, &vars).unwrap(),
json!(3)
);
assert_eq!(
eval_expr(&json!({ "$size": "$empty" }), &doc, &vars).unwrap(),
json!(0)
);
assert!(eval_expr(&json!({ "$size": "$n" }), &doc, &vars).is_err());
assert!(eval_expr(&json!({ "$size": "$missing" }), &doc, &vars).is_err());
}
#[test]
fn expr_sum_avg_min_max_array_form() {
let vars = Map::new();
let doc = json!({
"scores": [3, 7, 5],
"mixed": [1, "x", 4, null],
"empty": [],
"gone": null,
"one": 9
});
assert_eq!(
eval_expr(&json!({ "$sum": "$scores" }), &doc, &vars).unwrap(),
json!(15)
);
assert_eq!(
eval_expr(&json!({ "$sum": "$mixed" }), &doc, &vars).unwrap(),
json!(5)
);
assert_eq!(
eval_expr(&json!({ "$sum": "$empty" }), &doc, &vars).unwrap(),
json!(0)
);
assert_eq!(
eval_expr(&json!({ "$sum": "$gone" }), &doc, &vars).unwrap(),
json!(0)
);
assert_eq!(
eval_expr(&json!({ "$sum": "$missing" }), &doc, &vars).unwrap(),
json!(0)
);
assert_eq!(
eval_expr(&json!({ "$avg": "$scores" }), &doc, &vars).unwrap(),
json!(5.0)
);
assert_eq!(
eval_expr(&json!({ "$avg": "$empty" }), &doc, &vars).unwrap(),
Value::Null
);
assert_eq!(
eval_expr(&json!({ "$avg": "$missing" }), &doc, &vars).unwrap(),
Value::Null
);
assert_eq!(
eval_expr(&json!({ "$min": "$scores" }), &doc, &vars).unwrap(),
json!(3)
);
assert_eq!(
eval_expr(&json!({ "$max": "$scores" }), &doc, &vars).unwrap(),
json!(7)
);
assert_eq!(
eval_expr(&json!({ "$min": "$mixed" }), &doc, &vars).unwrap(),
json!(1)
);
assert_eq!(
eval_expr(&json!({ "$min": "$empty" }), &doc, &vars).unwrap(),
Value::Null
);
assert_eq!(
eval_expr(&json!({ "$sum": "$one" }), &doc, &vars).unwrap(),
json!(9)
);
assert_eq!(
eval_expr(&json!({ "$avg": "$one" }), &doc, &vars).unwrap(),
json!(9.0)
);
assert_eq!(
eval_expr(&json!({ "$min": "$one" }), &doc, &vars).unwrap(),
json!(9)
);
}
#[test]
fn projection_include_nested() {
let doc = json!({ "_id": 7, "a": { "b": 1, "c": 2 }, "d": 3 });
assert_eq!(
apply_projection(&doc, &json!({ "a.b": 1 })).unwrap(),
json!({ "_id": 7, "a": { "b": 1 } })
);
assert_eq!(
apply_projection(&doc, &json!({ "d": 1 })).unwrap(),
json!({ "_id": 7, "d": 3 })
);
assert_eq!(
apply_projection(&doc, &json!({ "d": 1, "_id": 0 })).unwrap(),
json!({ "d": 3 })
);
}
#[test]
fn projection_exclude_and_errors() {
let doc = json!({ "_id": 7, "a": { "b": 1, "c": 2 }, "d": 3 });
assert_eq!(
apply_projection(&doc, &json!({ "a.b": 0 })).unwrap(),
json!({ "_id": 7, "a": { "c": 2 }, "d": 3 })
);
assert_eq!(
apply_projection(&doc, &json!({ "_id": 0 })).unwrap(),
json!({ "a": { "b": 1, "c": 2 }, "d": 3 })
);
assert!(apply_projection(&doc, &json!({ "a": 1, "d": 0 })).is_err());
assert!(apply_projection(&doc, &json!({ "a": 2 })).is_err());
}
}