Skip to main content

rust_store_core/local/
value.rs

1//! 本地磁盘数据源 —— 值工具(点号路径取值 / Mongo BSON 类型序比较 / 极小表达式 / 投影)。
2//!
3//! 语义基准 = **MongoDB 驱动语义**(local 与 mongo 走同一条命令路径,必须同结果)。
4//! 纯逻辑:无 IO / 无时钟 / 无随机(确定性是双端对拍的前提)。
5//!
6//! 三态契约:`显式 null`(`Value::Null`)与`字段缺失`是两种不同状态 ——
7//! [`resolve_path_candidates`] 以「空 = 缺失、含 `Null` = 显式 null」承载该区分。
8
9use std::cmp::Ordering;
10
11use serde_json::{Map, Value};
12
13use crate::bson;
14
15use super::pipeline::{float_of, number_of};
16
17/// 单集合文档数护栏:超限一律 `Err`(禁静默截断,见执行文档 §4.1 / §8.4)。
18pub const MAX_LOCAL_COLLECTION_DOCS: usize = 100_000;
19
20// ---------------------------------------------------------------------------
21// Mongo BSON 类型序权重(升序;跨类型比较的第一基准)
22// ---------------------------------------------------------------------------
23
24const RANK_MINKEY: u8 = 1;
25const RANK_NULL: u8 = 2;
26const RANK_NUMBER: u8 = 3;
27const RANK_STRING: u8 = 4;
28const RANK_OBJECT: u8 = 5;
29const RANK_ARRAY: u8 = 6;
30const RANK_BINARY: u8 = 7;
31const RANK_OID: u8 = 8;
32const RANK_BOOL: u8 = 9;
33const RANK_DATE: u8 = 10;
34const RANK_TIMESTAMP: u8 = 11;
35const RANK_REGEX: u8 = 12;
36const RANK_MAXKEY: u8 = 13;
37
38/// 把点号路径拆段(`a.b.c` → `["a","b","c"]`;空路径 → 空段列表)。
39fn segments(path: &str) -> Vec<&str> {
40    if path.is_empty() {
41        Vec::new()
42    } else {
43        path.split('.').collect()
44    }
45}
46
47// ---------------------------------------------------------------------------
48// 点号路径取值
49// ---------------------------------------------------------------------------
50
51/// 严格点号取值(**不展开数组**):路径任一段缺失 → `None`;显式 `null` → `Some(&Value::Null)`。
52///
53/// 「缺失」与「显式 null」由此区分(三态契约的基础)。空路径 → 整文档。
54/// 数组下钻语义由 [`resolve_path_candidates`] 承担。
55pub fn get_path<'a>(doc: &'a Value, path: &str) -> Option<&'a Value> {
56    let mut cur = doc;
57    for seg in segments(path) {
58        cur = match cur {
59            Value::Object(o) => o.get(seg)?,
60            // 严格取值:遇数组/标量不再下钻
61            _ => return None,
62        };
63    }
64    Some(cur)
65}
66
67/// 解析字段路径在文档上的**全部候选值**(Mongo 过滤命中语义,含数组下钻)。
68///
69/// 规则(对齐 MongoDB 路径匹配):
70/// - 对象 → 取键、消费一段;
71/// - 数组 → **不消费段**,对每个元素继续解析(用于 `a.b` 中 `a` 为文档数组);
72/// - 段缺失 / 非对象非数组 → 该分支终止、不产出。
73///
74/// 返回:空 = 路径缺失(对应 `$exists:false` / 不命中);含 `Value::Null` = 存在显式 null。
75/// 仅返回末段解析到的值本身(数组字段返回该数组整体,元素命中由调用方按 [`matches_value`] 判定)。
76pub fn resolve_path_candidates<'a>(doc: &'a Value, path: &str) -> Vec<&'a Value> {
77    let mut out = Vec::new();
78    collect_candidates(doc, &segments(path), &mut out);
79    out
80}
81
82fn collect_candidates<'a>(v: &'a Value, segs: &[&str], out: &mut Vec<&'a Value>) {
83    if segs.is_empty() {
84        out.push(v);
85        return;
86    }
87    match v {
88        Value::Object(o) => {
89            if let Some(next) = o.get(segs[0]) {
90                collect_candidates(next, &segs[1..], out);
91            }
92        }
93        Value::Array(a) => {
94            for el in a {
95                collect_candidates(el, segs, out);
96            }
97        }
98        _ => {}
99    }
100}
101
102// ---------------------------------------------------------------------------
103// Mongo BSON 类型序比较
104// ---------------------------------------------------------------------------
105
106/// BSON 类型序权重:跨类型比较时先比权重(`MinKey < Null < Number < String < Object < Array < …`)。
107///
108/// 扩展类型(`$numberDecimal` / `$numberLong`)归入 Number;`$minKey` / `$maxKey` / `$timestamp` /
109/// `$regularExpression` 各归其序。
110pub fn bson_rank(v: &Value) -> u8 {
111    match v {
112        Value::Null => RANK_NULL,
113        Value::Bool(_) => RANK_BOOL,
114        Value::Number(_) => RANK_NUMBER,
115        Value::String(_) => RANK_STRING,
116        Value::Array(_) => RANK_ARRAY,
117        Value::Object(o) => extended_kind(o).unwrap_or(RANK_OBJECT),
118    }
119}
120
121/// 单键扩展类型对象 → 其 BSON 序权重;非扩展对象 → `None`(按普通文档处理)。
122fn extended_kind(o: &Map<String, Value>) -> Option<u8> {
123    if o.len() != 1 {
124        return None;
125    }
126    match o.keys().next()?.as_str() {
127        "$minKey" => Some(RANK_MINKEY),
128        "$maxKey" => Some(RANK_MAXKEY),
129        "$numberDecimal" | "$numberLong" => Some(RANK_NUMBER),
130        "$oid" => Some(RANK_OID),
131        "$date" => Some(RANK_DATE),
132        "$timestamp" => Some(RANK_TIMESTAMP),
133        "$binary" => Some(RANK_BINARY),
134        "$regularExpression" => Some(RANK_REGEX),
135        _ => None,
136    }
137}
138
139/// Mongo BSON 序比较(`Ordering::Less/Greater/Equal`)。
140///
141/// 数字间按数值比较(整型/浮点/扩展 Long 统一,`1` 与 `1.0` 相等);
142/// 字符串按字节序;文档逐键(键名 → 键值)比较,前缀相等时短者小;数组逐元素、前缀相等时短者小。
143pub fn compare(a: &Value, b: &Value) -> Ordering {
144    let (ra, rb) = (bson_rank(a), bson_rank(b));
145    if ra != rb {
146        return ra.cmp(&rb);
147    }
148    match ra {
149        RANK_NUMBER => compare_numbers(a, b),
150        RANK_STRING => a.as_str().unwrap_or("").cmp(b.as_str().unwrap_or("")),
151        RANK_BOOL => bool_rank(a).cmp(&bool_rank(b)),
152        RANK_OBJECT => compare_objects(
153            a.as_object().expect("rank=object"),
154            b.as_object().expect("rank=object"),
155        ),
156        RANK_ARRAY => compare_arrays(
157            a.as_array().expect("rank=array"),
158            b.as_array().expect("rank=array"),
159        ),
160        RANK_OID => oid_str(a).cmp(oid_str(b)),
161        RANK_DATE => date_ms(a).cmp(&date_ms(b)),
162        RANK_TIMESTAMP => ts_parts(a).cmp(&ts_parts(b)),
163        RANK_BINARY => compare_binary(a, b),
164        RANK_REGEX => regex_parts(a).cmp(&regex_parts(b)),
165        // Null / MinKey / MaxKey:同类型彼此相等
166        _ => Ordering::Equal,
167    }
168}
169
170/// 等值判定(= BSON 序比较为 `Equal`)。数字跨整型/浮点相等;文档按结构(键序由 serde_json 归一)。
171pub fn values_equal(a: &Value, b: &Value) -> bool {
172    compare(a, b) == Ordering::Equal
173}
174
175/// Mongo 等值命中:`actual` 等于 `expected`,或 `actual` 为**数组**且任一元素等于 `expected`。
176///
177/// 供过滤层 `{f: v}` / `$eq` 的「数组字段任一元素命中」语义复用。
178pub fn matches_value(actual: &Value, expected: &Value) -> bool {
179    if values_equal(actual, expected) {
180        return true;
181    }
182    match actual {
183        Value::Array(a) => a.iter().any(|el| values_equal(el, expected)),
184        _ => false,
185    }
186}
187
188fn compare_numbers(a: &Value, b: &Value) -> Ordering {
189    numeric_value(a)
190        .partial_cmp(&numeric_value(b))
191        .unwrap_or(Ordering::Equal)
192}
193
194/// 数字取值(普通 JSON number / 扩展 `$numberLong` / `$numberDecimal` 统一为 f64);非数字 → `None`。
195///
196/// 与 [`numeric_value`] 的差别:本函数对非数字返回 `None`(供聚合累积器判定「是否忽略该值」),
197/// 后者返回 `0.0`(供比较排序,任何值都可参与)。
198pub fn as_number(v: &Value) -> Option<f64> {
199    match v {
200        Value::Number(n) => n.as_f64(),
201        Value::Object(o) => {
202            if let Some(s) = o.get("$numberLong").and_then(Value::as_str) {
203                s.parse::<f64>().ok()
204            } else {
205                o.get("$numberDecimal")
206                    .and_then(Value::as_str)
207                    .and_then(|s| s.parse::<f64>().ok())
208            }
209        }
210        _ => None,
211    }
212}
213
214/// 数字的数值形式(普通 JSON number / 扩展 `$numberLong` / `$numberDecimal` 统一为 f64)。
215fn numeric_value(v: &Value) -> f64 {
216    as_number(v).unwrap_or(0.0)
217}
218
219fn bool_rank(v: &Value) -> u8 {
220    u8::from(v.as_bool().unwrap_or(false))
221}
222
223fn compare_objects(a: &Map<String, Value>, b: &Map<String, Value>) -> Ordering {
224    let mut ai = a.iter();
225    let mut bi = b.iter();
226    loop {
227        match (ai.next(), bi.next()) {
228            (None, None) => return Ordering::Equal,
229            (None, Some(_)) => return Ordering::Less,
230            (Some(_), None) => return Ordering::Greater,
231            (Some((ka, va)), Some((kb, vb))) => {
232                let key_cmp = ka.cmp(kb);
233                if key_cmp != Ordering::Equal {
234                    return key_cmp;
235                }
236                let val_cmp = compare(va, vb);
237                if val_cmp != Ordering::Equal {
238                    return val_cmp;
239                }
240            }
241        }
242    }
243}
244
245fn compare_arrays(a: &[Value], b: &[Value]) -> Ordering {
246    for (x, y) in a.iter().zip(b.iter()) {
247        let c = compare(x, y);
248        if c != Ordering::Equal {
249            return c;
250        }
251    }
252    a.len().cmp(&b.len())
253}
254
255fn oid_str(v: &Value) -> &str {
256    v.get("$oid").and_then(Value::as_str).unwrap_or("")
257}
258
259fn date_ms(v: &Value) -> i64 {
260    v.get("$date").and_then(Value::as_i64).unwrap_or(0)
261}
262
263fn ts_parts(v: &Value) -> (i64, i64) {
264    match v.get("$timestamp").and_then(Value::as_object) {
265        Some(o) => (
266            o.get("t").and_then(Value::as_i64).unwrap_or(0),
267            o.get("i").and_then(Value::as_i64).unwrap_or(0),
268        ),
269        None => (0, 0),
270    }
271}
272
273fn compare_binary(a: &Value, b: &Value) -> Ordering {
274    // Mongo 序:先按数据长度、再按子类型、再按字节(base64 长度与数据长度成比例,用作长度基准)
275    let (a_b64, a_sub) = binary_parts(a);
276    let (b_b64, b_sub) = binary_parts(b);
277    a_b64
278        .len()
279        .cmp(&b_b64.len())
280        .then_with(|| a_sub.cmp(&b_sub))
281        .then_with(|| a_b64.cmp(&b_b64))
282}
283
284fn binary_parts(v: &Value) -> (String, String) {
285    match v.get("$binary").and_then(Value::as_object) {
286        Some(o) => (
287            o.get("base64")
288                .and_then(Value::as_str)
289                .unwrap_or("")
290                .to_string(),
291            o.get("subType")
292                .and_then(Value::as_str)
293                .unwrap_or("")
294                .to_string(),
295        ),
296        None => (String::new(), String::new()),
297    }
298}
299
300fn regex_parts(v: &Value) -> (String, String) {
301    match v.get("$regularExpression").and_then(Value::as_object) {
302        Some(o) => (
303            o.get("pattern")
304                .and_then(Value::as_str)
305                .unwrap_or("")
306                .to_string(),
307            o.get("options")
308                .and_then(Value::as_str)
309                .unwrap_or("")
310                .to_string(),
311        ),
312        None => (String::new(), String::new()),
313    }
314}
315
316// ---------------------------------------------------------------------------
317// 极小聚合表达式求值(`$expr` / `$lookup.pipeline` / `$group` 键 / `$project` 计算字段)
318// ---------------------------------------------------------------------------
319
320/// 求值极小聚合表达式。
321///
322/// 支持:
323/// - 字面量(数字 / 字符串 / 布尔 / null / 数组 / 扩展类型对象)原样返回;
324/// - `"$field.path"`:从 `doc` 严格取值,缺失 → `null`;
325/// - `"$$var"` / `"$$var.path"`:从 `vars` 取值,缺失 → `null`(供 `$lookup.let` 注入);
326/// - `{ "$eq": [a, b] }`、`{ "$and": [...] }`、`{ "$or": [...] }`、`{ "$ifNull": [a, b] }`;
327/// - `{ "$size": <数组> }`、`{ "$sum/$avg/$min/$max": <数组或单值> }`
328///   (core 关系聚合计算列 `$addFields` 的表达式形态,语义对齐 `pipeline.rs::accumulate`);
329/// - `{ "$ne": [a, b] }`、`{ "$filter": {input, as?, cond} }`、`{ "$setUnion": [...] }`
330///   (inline-array agg 计算列 `$countDistinct` 编译形态的求值面,04);
331/// - 普通对象 `{k: <expr>, …}` 逐键求值构造新文档;普通数组逐元素求值。
332///
333/// 未知算子一律 `Err`(禁静默)。
334pub fn eval_expr(expr: &Value, doc: &Value, vars: &Map<String, Value>) -> Result<Value, String> {
335    match expr {
336        Value::String(s) => eval_ref(s, doc, vars),
337        Value::Array(a) => a
338            .iter()
339            .map(|e| eval_expr(e, doc, vars))
340            .collect::<Result<Vec<_>, _>>()
341            .map(Value::Array),
342        Value::Object(o) => {
343            // 扩展类型对象(`$oid` 等)原样返回,避免被误判为算子
344            if bson::is_extended(expr) {
345                return Ok(expr.clone());
346            }
347            if o.len() == 1 {
348                if let Some(op) = o.keys().next().map(String::as_str) {
349                    if let Some(stripped) = op.strip_prefix('$') {
350                        return eval_operator(
351                            stripped,
352                            o.values().next().expect("len=1"),
353                            doc,
354                            vars,
355                        );
356                    }
357                }
358            }
359            let mut out = Map::new();
360            for (k, v) in o {
361                out.insert(k.clone(), eval_expr(v, doc, vars)?);
362            }
363            Ok(Value::Object(out))
364        }
365        other => Ok(other.clone()),
366    }
367}
368
369fn eval_operator(
370    op: &str,
371    arg: &Value,
372    doc: &Value,
373    vars: &Map<String, Value>,
374) -> Result<Value, String> {
375    match op {
376        "eq" => {
377            let (a, b) = two_operands("$eq", arg)?;
378            Ok(Value::Bool(values_equal(
379                &eval_expr(a, doc, vars)?,
380                &eval_expr(b, doc, vars)?,
381            )))
382        }
383        "and" => {
384            for e in array_operands("$and", arg)? {
385                if !truthy(&eval_expr(e, doc, vars)?) {
386                    return Ok(Value::Bool(false));
387                }
388            }
389            Ok(Value::Bool(true))
390        }
391        "or" => {
392            for e in array_operands("$or", arg)? {
393                if truthy(&eval_expr(e, doc, vars)?) {
394                    return Ok(Value::Bool(true));
395                }
396            }
397            Ok(Value::Bool(false))
398        }
399        "ifNull" => {
400            let (a, b) = two_operands("$ifNull", arg)?;
401            let v = eval_expr(a, doc, vars)?;
402            if v.is_null() {
403                eval_expr(b, doc, vars)
404            } else {
405                Ok(v)
406            }
407        }
408        // `$cond`:core 的 `$lookup.let`(数组外键守卫)与 `$group` 的 `$count` 字段形态
409        // 均发射该算子,故必须支持;数组形态 `[if, then, else]` 与对象形态 `{if,then,else}` 均可。
410        "cond" => {
411            let (c, t, e) = cond_operands(arg)?;
412            if truthy(&eval_expr(c, doc, vars)?) {
413                eval_expr(t, doc, vars)
414            } else {
415                eval_expr(e, doc, vars)
416            }
417        }
418        "isArray" => Ok(Value::Bool(eval_expr(arg, doc, vars)?.is_array())),
419        // `$in`:core 数组外键关系发射 `{$expr: {$in: ["$fk", "$$let"]}}`
420        "in" => {
421            let (needle, hay) = two_operands("$in", arg)?;
422            let needle = eval_expr(needle, doc, vars)?;
423            let hay = eval_expr(hay, doc, vars)?;
424            let items = hay
425                .as_array()
426                .ok_or_else(|| "$in 的第二个操作数必须是数组".to_string())?;
427            let hit = match &needle {
428                Value::Array(a) => a
429                    .iter()
430                    .any(|n| items.iter().any(|c| values_equal(n, c))),
431                other => items.iter().any(|c| values_equal(other, c)),
432            };
433            Ok(Value::Bool(hit))
434        }
435        // `$arrayElemAt`:core 的全表单组空集护栏(`$facet` + `$replaceRoot`)使用
436        "arrayElemAt" => {
437            let (arr_e, idx_e) = two_operands("$arrayElemAt", arg)?;
438            let arr = eval_expr(arr_e, doc, vars)?;
439            let idx_v = eval_expr(idx_e, doc, vars)?;
440            let idx = idx_v
441                .as_i64()
442                .ok_or_else(|| "$arrayElemAt 的下标必须是整数".to_string())?;
443            let items = arr
444                .as_array()
445                .ok_or_else(|| "$arrayElemAt 的第一个操作数必须是数组".to_string())?;
446            let len = items.len() as i64;
447            let i = if idx < 0 { len + idx } else { idx };
448            // 越界 → `null`(Mongo 为 missing,在 `$ifNull` 兜底语境下等价)
449            Ok(items.get(i as usize).cloned().unwrap_or(Value::Null))
450        }
451        // `$ne` / `$filter` / `$setUnion`:inline-array agg 计算列 `$countDistinct`
452        // 编译形态 `{$size:{$setUnion:[{$filter:{input,as,cond:{$ne:["$$v",null]}}},[]]}}`
453        // 的求值面(漉金Core 接入扩展 04)。
454        "ne" => {
455            let (a, b) = two_operands("$ne", arg)?;
456            Ok(Value::Bool(!values_equal(
457                &eval_expr(a, doc, vars)?,
458                &eval_expr(b, doc, vars)?,
459            )))
460        }
461        "filter" => {
462            let obj = arg
463                .as_object()
464                .ok_or("$filter 的参数必须是对象 {input, as?, cond}")?;
465            let input = obj.get("input").ok_or("$filter 缺少 input")?;
466            let cond = obj.get("cond").ok_or("$filter 缺少 cond")?;
467            let as_name = obj.get("as").and_then(|v| v.as_str()).unwrap_or("this");
468            let arr = eval_expr(input, doc, vars)?;
469            let items = arr.as_array().ok_or_else(|| {
470                format!(
471                    "$filter 的 input 必须是数组,收到 {}",
472                    value_type_name(&arr)
473                )
474            })?;
475            let mut out = Vec::with_capacity(items.len());
476            for item in items {
477                let mut inner_vars = vars.clone();
478                inner_vars.insert(as_name.to_string(), item.clone());
479                if truthy(&eval_expr(cond, doc, &inner_vars)?) {
480                    out.push(item.clone());
481                }
482            }
483            Ok(Value::Array(out))
484        }
485        "setUnion" => {
486            // 按序并集去重(线性判等;Mongo 语义:null 参数报错,null 元素保留——
487            // 剔 null 由上游 $filter 完成)
488            let mut out: Vec<Value> = Vec::new();
489            for e in array_operands("$setUnion", arg)? {
490                let v = eval_expr(e, doc, vars)?;
491                let items = v.as_array().ok_or_else(|| {
492                    format!(
493                        "$setUnion 的操作数必须是数组,收到 {}",
494                        value_type_name(&v)
495                    )
496                })?;
497                for item in items {
498                    if !out.iter().any(|c| values_equal(item, c)) {
499                        out.push(item.clone());
500                    }
501                }
502            }
503            Ok(Value::Array(out))
504        }
505        // `$size`:数组长度(core 关系聚合计算列 `$count` 发射 `{$size: {$ifNull: [..., []]}}`)
506        "size" => {
507            let arr = eval_expr(arg, doc, vars)?;
508            let items = arr
509                .as_array()
510                .ok_or_else(|| format!("$size 的操作数必须是数组,收到 {}", value_type_name(&arr)))?;
511            Ok(Value::from(items.len() as i64))
512        }
513        // `$sum` / `$avg` / `$min` / `$max`:数组聚合表达式形态(core 关系聚合计算列
514        // `$addFields` 发射 `{$sum/$avg/$min/$max: "$as.field"}`,`as` 为 `$lookup` 结果数组)。
515        // 语义对齐 `pipeline.rs::accumulate`($sum 忽略非数值、整数值→整数形态;$avg 恒浮点;
516        // $min/$max 按 BSON 序、忽略 null;空集 → $sum=0 / 其余 null)。单值形态 = 单元素数组。
517        "sum" | "avg" | "min" | "max" => {
518            let v = eval_expr(arg, doc, vars)?;
519            match v {
520                Value::Array(items) => accumulate_expr(op, &items),
521                other => accumulate_expr(op, std::slice::from_ref(&other)),
522            }
523        }
524        other => Err(format!(
525            "不支持的聚合表达式算子: ${other}(本地求值器仅支持 $eq/$and/$or/$ifNull/$cond/$isArray/$in/$arrayElemAt/$size/$sum/$avg/$min/$max/$ne/$filter/$setUnion,拒绝静默)"
526        )),
527    }
528}
529
530/// 数组聚合(`$sum` / `$avg` / `$min` / `$max` 的表达式形态)——语义镜像
531/// [`super::pipeline::accumulate`],保证「关系聚合计算列」与「根级 `$group`」数值形态一致。
532fn accumulate_expr(op: &str, items: &[Value]) -> Result<Value, String> {
533    match op {
534        "sum" => {
535            let mut sum = 0.0_f64;
536            for v in items {
537                if let Some(n) = as_number(v) {
538                    sum += n;
539                }
540            }
541            Ok(number_of(sum))
542        }
543        "avg" => {
544            let (mut sum, mut count) = (0.0_f64, 0_usize);
545            for v in items {
546                if let Some(n) = as_number(v) {
547                    sum += n;
548                    count += 1;
549                }
550            }
551            Ok(if count == 0 {
552                Value::Null
553            } else {
554                float_of(sum / count as f64)
555            })
556        }
557        _ => {
558            let mut best: Option<&Value> = None;
559            for v in items {
560                if v.is_null() {
561                    continue;
562                }
563                best = Some(match best {
564                    None => v,
565                    Some(cur) => {
566                        let take = if op == "min" {
567                            compare(v, cur) == Ordering::Less
568                        } else {
569                            compare(v, cur) == Ordering::Greater
570                        };
571                        if take {
572                            v
573                        } else {
574                            cur
575                        }
576                    }
577                });
578            }
579            Ok(best.cloned().unwrap_or(Value::Null))
580        }
581    }
582}
583
584fn value_type_name(v: &Value) -> &'static str {
585    match v {
586        Value::Null => "null",
587        Value::Bool(_) => "bool",
588        Value::Number(_) => "number",
589        Value::String(_) => "string",
590        Value::Array(_) => "array",
591        Value::Object(_) => "object",
592    }
593}
594
595fn two_operands<'a>(name: &str, arg: &'a Value) -> Result<(&'a Value, &'a Value), String> {
596    let arr = arg
597        .as_array()
598        .ok_or_else(|| format!("{name} 需要操作数数组"))?;
599    if arr.len() != 2 {
600        return Err(format!("{name} 需要恰好两个操作数,收到 {}", arr.len()));
601    }
602    Ok((&arr[0], &arr[1]))
603}
604
605fn array_operands<'a>(name: &str, arg: &'a Value) -> Result<&'a [Value], String> {
606    arg.as_array()
607        .map(Vec::as_slice)
608        .ok_or_else(|| format!("{name} 需要操作数数组"))
609}
610
611/// `$cond` 操作数:数组形态 `[if, then, else]` 或对象形态 `{if, then, else}`。
612fn cond_operands(arg: &Value) -> Result<(&Value, &Value, &Value), String> {
613    if let Some(a) = arg.as_array() {
614        if a.len() != 3 {
615            return Err(format!("$cond 数组形态需要 3 个操作数,收到 {}", a.len()));
616        }
617        return Ok((&a[0], &a[1], &a[2]));
618    }
619    if let Some(o) = arg.as_object() {
620        let pick = |k: &str| {
621            o.get(k)
622                .ok_or_else(|| format!("$cond 对象形态缺少 \"{k}\""))
623        };
624        return Ok((pick("if")?, pick("then")?, pick("else")?));
625    }
626    Err("$cond 需要数组 [if, then, else] 或对象 {if, then, else}".to_string())
627}
628
629/// Mongo 聚合布尔真值:`false` / `null` / `0` 为假,其余为真。
630fn truthy(v: &Value) -> bool {
631    match v {
632        Value::Null => false,
633        Value::Bool(b) => *b,
634        Value::Number(n) => n.as_f64().map(|x| x != 0.0).unwrap_or(true),
635        _ => true,
636    }
637}
638
639/// `"$field"` / `"$$var"` 引用求值;非 `$` 前缀的字符串为字面量。
640fn eval_ref(s: &str, doc: &Value, vars: &Map<String, Value>) -> Result<Value, String> {
641    if let Some(rest) = s.strip_prefix("$$") {
642        let (name, path) = match rest.split_once('.') {
643            Some((n, p)) => (n, p),
644            None => (rest, ""),
645        };
646        let base = vars.get(name).cloned().unwrap_or(Value::Null);
647        Ok(reference_path(&base, path))
648    } else if let Some(rest) = s.strip_prefix('$') {
649        Ok(eval_field_path(doc, rest))
650    } else {
651        Ok(Value::String(s.to_string()))
652    }
653}
654
655/// `"$a.b"` 字段引用的**表达式**求值(Mongo 表达式语义,含数组展开):
656/// - 纯对象路径 → 值本身;缺失 → `Null`(与 [`get_path`] 的三态形态一致);
657/// - 路径中间段命中数组 → 扇出:对每个元素继续解析剩余段(元素为对象才取段),
658///   缺失子路径的元素跳过,结果恒为数组(单/零元素也保持数组——对齐 Mongo
659///   `$a.b` 在 `a` 为数组时的投影形态,`{a:[]}` → `[]` 而非 missing)。
660///
661/// 与 [`get_path`](过滤匹配的严格取值)/ [`resolve_path_candidates`](命中候选收集)
662/// 的差别:本函数服务**投影/聚合表达式**的引用求值——关系聚合计算列与 inline-array
663/// agg(漉金Core 接入扩展 04)都发射 `{$max:"$arr.f"}` / `{$ifNull:["$arr.f",[]]}`
664/// 形态,依赖数组展开取元素列(漉金Core 接入扩展 04 修复:此前走 get_path 遇数组
665/// 即缺失,聚合恒 null/0)。`$$var` 引用不展开($lookup let 绑定值均为标量)。
666fn eval_field_path(doc: &Value, path: &str) -> Value {
667    let mut cur: Vec<&Value> = vec![doc];
668    let mut fanout = false;
669    for seg in segments(path) {
670        let mut next: Vec<&Value> = Vec::new();
671        let mut array_hit = false;
672        for base in &cur {
673            match base {
674                Value::Object(o) => {
675                    if let Some(v) = o.get(seg) {
676                        next.push(v);
677                    }
678                }
679                Value::Array(a) => {
680                    array_hit = true;
681                    for el in a {
682                        if let Value::Object(o) = el {
683                            if let Some(v) = o.get(seg) {
684                                next.push(v);
685                            }
686                        }
687                    }
688                }
689                _ => {}
690            }
691        }
692        if array_hit {
693            fanout = true;
694        }
695        cur = next;
696        if cur.is_empty() {
697            break;
698        }
699    }
700    if fanout {
701        Value::Array(cur.into_iter().cloned().collect())
702    } else {
703        cur.into_iter().next().cloned().unwrap_or(Value::Null)
704    }
705}
706
707fn reference_path(base: &Value, path: &str) -> Value {
708    if path.is_empty() {
709        base.clone()
710    } else {
711        get_path(base, path).cloned().unwrap_or(Value::Null)
712    }
713}
714
715// ---------------------------------------------------------------------------
716// 投影(包含 / 排除;`_id` 可与包含式混用)
717// ---------------------------------------------------------------------------
718
719/// 应用投影:包含式 / 排除式;`_id` 特殊处理(包含式默认带 `_id`,`_id: 0` 可与之混用)。
720///
721/// 空投影对象 → 原样返回(视为无投影)。同时含非 `_id` 的包含与排除 → `Err`(禁静默)。
722pub fn apply_projection(doc: &Value, projection: &Value) -> Result<Value, String> {
723    let obj = projection
724        .as_object()
725        .ok_or_else(|| format!("投影必须是对象,收到 {}", type_name(projection)))?;
726    if obj.is_empty() {
727        return Ok(doc.clone());
728    }
729
730    let mut include_paths: Vec<&String> = Vec::new();
731    let mut exclude_paths: Vec<&String> = Vec::new();
732    let mut id: Option<bool> = None;
733    for (k, v) in obj {
734        let flag = projection_flag(v)?;
735        if k == "_id" {
736            id = Some(flag);
737        } else if flag {
738            include_paths.push(k);
739        } else {
740            exclude_paths.push(k);
741        }
742    }
743    if !include_paths.is_empty() && !exclude_paths.is_empty() {
744        return Err("投影不允许同时包含与排除字段(`_id` 除外)".to_string());
745    }
746
747    let include_mode = if !include_paths.is_empty() {
748        true
749    } else if !exclude_paths.is_empty() {
750        false
751    } else {
752        // 仅出现 `_id` 指令:`_id:1` → 只保留 `_id`;`_id:0` → 全保留除 `_id`
753        id == Some(true)
754    };
755
756    if include_mode {
757        let mut out = Map::new();
758        if id != Some(false) {
759            if let Some(v) = doc.get("_id") {
760                out.insert("_id".to_string(), v.clone());
761            }
762        }
763        for path in &include_paths {
764            if let Some(v) = project_included(doc, &segments(path)) {
765                merge_into(&mut out, v);
766            }
767        }
768        Ok(Value::Object(out))
769    } else {
770        let mut out = doc.clone();
771        for path in &exclude_paths {
772            remove_segments(&mut out, &segments(path));
773        }
774        if id == Some(false) {
775            if let Value::Object(m) = &mut out {
776                m.remove("_id");
777            }
778        }
779        Ok(out)
780    }
781}
782
783/// 投影值 → 包含(bool=true) / 排除(bool=false);仅接受 `0/1` 与 `false/true`。
784fn projection_flag(v: &Value) -> Result<bool, String> {
785    match v {
786        Value::Bool(b) => Ok(*b),
787        Value::Number(n) => match (n.as_i64(), n.as_f64()) {
788            (Some(0), _) | (_, Some(0.0)) => Ok(false),
789            (Some(1), _) | (_, Some(1.0)) => Ok(true),
790            _ => Err(format!("投影值必须是 0/1 或 false/true,收到 {n}")),
791        },
792        other => Err(format!(
793            "不支持的投影形态: {other}(本地求值器仅支持包含/排除,拒绝静默)"
794        )),
795    }
796}
797
798/// 按剩余段对 `doc` 做包含投影,返回投影后的子树;路径不存在 → `None`。
799/// 遇数组:逐元素按同段继续(Mongo 数组映射语义的简化),无命中项跳过。
800fn project_included(doc: &Value, segs: &[&str]) -> Option<Value> {
801    if segs.is_empty() {
802        return Some(doc.clone());
803    }
804    match doc {
805        Value::Object(o) => {
806            let child = o.get(segs[0])?;
807            let sub = project_included(child, &segs[1..])?;
808            let mut m = Map::new();
809            m.insert(segs[0].to_string(), sub);
810            Some(Value::Object(m))
811        }
812        Value::Array(a) => {
813            let mut arr = Vec::with_capacity(a.len());
814            for el in a {
815                if let Some(v) = project_included(el, segs) {
816                    arr.push(v);
817                }
818            }
819            Some(Value::Array(arr))
820        }
821        _ => None,
822    }
823}
824
825/// 包含式取字段(点号路径,嵌套还原);路径不存在 → `None`。
826///
827/// 供聚合投影(`$project` 含表达式值)按路径并入输出文档使用。
828pub fn project_include(doc: &Value, path: &str) -> Option<Value> {
829    project_included(doc, &segments(path))
830}
831
832/// 把投影子树并入输出文档;同名对象递归合并,其余覆盖。
833pub fn merge_into(dst: &mut Map<String, Value>, src: Value) {
834    let Value::Object(src) = src else {
835        return;
836    };
837    for (k, v) in src {
838        let nested = matches!(dst.get(&k), Some(Value::Object(_))) && v.is_object();
839        if nested {
840            if let Some(Value::Object(d)) = dst.get_mut(&k) {
841                merge_into(d, v);
842            }
843        } else {
844            dst.insert(k, v);
845        }
846    }
847}
848
849/// 按段做排除投影(原地);遇数组对每个元素继续。
850pub fn remove_path(doc: &mut Value, path: &str) {
851    remove_segments(doc, &segments(path));
852}
853
854/// 按段路径写入值(`$addFields` / `$unwind` 用):中间层不存在或非对象时补建为对象。
855pub fn set_path(doc: &mut Value, path: &str, val: Value) {
856    set_segments(doc, &segments(path), val);
857}
858
859fn set_segments(cur: &mut Value, segs: &[&str], val: Value) {
860    if segs.is_empty() {
861        *cur = val;
862        return;
863    }
864    if !cur.is_object() {
865        *cur = Value::Object(Map::new());
866    }
867    let Value::Object(o) = cur else { return };
868    if segs.len() == 1 {
869        o.insert(segs[0].to_string(), val);
870        return;
871    }
872    let entry = o
873        .entry(segs[0].to_string())
874        .or_insert_with(|| Value::Object(Map::new()));
875    set_segments(entry, &segs[1..], val);
876}
877
878fn remove_segments(doc: &mut Value, segs: &[&str]) {
879    if segs.is_empty() {
880        return;
881    }
882    match doc {
883        Value::Object(o) => {
884            if segs.len() == 1 {
885                o.remove(segs[0]);
886            } else if let Some(v) = o.get_mut(segs[0]) {
887                remove_segments(v, &segs[1..]);
888            }
889        }
890        Value::Array(a) => {
891            for el in a {
892                remove_segments(el, segs);
893            }
894        }
895        _ => {}
896    }
897}
898
899fn type_name(v: &Value) -> &'static str {
900    match v {
901        Value::Null => "null",
902        Value::Bool(_) => "bool",
903        Value::Number(_) => "number",
904        Value::String(_) => "string",
905        Value::Array(_) => "array",
906        Value::Object(_) => "object",
907    }
908}
909
910#[cfg(test)]
911mod tests {
912    use super::*;
913    use serde_json::json;
914
915    #[test]
916    fn max_docs_guard_constant() {
917        assert_eq!(MAX_LOCAL_COLLECTION_DOCS, 100_000);
918    }
919
920    #[test]
921    fn get_path_three_states() {
922        let doc = json!({ "a": null, "b": { "c": 1 } });
923        // 显式 null:Some(Null);缺失:None
924        assert_eq!(get_path(&doc, "a"), Some(&Value::Null));
925        assert_eq!(get_path(&doc, "missing"), None);
926        assert_eq!(get_path(&doc, "b.missing"), None);
927        assert_eq!(get_path(&doc, "b.c"), Some(&json!(1)));
928        // 严格取值不下钻数组
929        let arr = json!({ "a": [ { "b": 1 } ] });
930        assert_eq!(get_path(&arr, "a.b"), None);
931    }
932
933    #[test]
934    fn candidates_three_states_and_array_expansion() {
935        let doc = json!({ "a": null, "arr": [ { "b": 1 }, { "b": 2 } ] });
936        // 缺失 → 空;显式 null → [Null]
937        assert!(resolve_path_candidates(&doc, "missing").is_empty());
938        assert_eq!(resolve_path_candidates(&doc, "a"), vec![&Value::Null]);
939        // 数组下钻:a.b 命中每个元素
940        assert_eq!(
941            resolve_path_candidates(&doc, "arr.b"),
942            vec![&json!(1), &json!(2)]
943        );
944        // 末段为数组字段:候选为数组整体
945        let tags = json!({ "tags": ["x", "y"] });
946        assert_eq!(
947            resolve_path_candidates(&tags, "tags"),
948            vec![&json!(["x", "y"])]
949        );
950    }
951
952    #[test]
953    fn bson_type_order_across_types() {
954        let min = json!({ "$minKey": 1 });
955        let null = Value::Null;
956        let num = json!(5);
957        let s = json!("abc");
958        let obj = json!({ "x": 1 });
959        let arr = json!([1, 2]);
960        let boolean = json!(true);
961        let date = json!({ "$date": 0 });
962        let max = json!({ "$maxKey": 1 });
963        let seq = [&min, &null, &num, &s, &obj, &arr, &boolean, &date, &max];
964        for (i, a) in seq.iter().enumerate() {
965            for (j, b) in seq.iter().enumerate() {
966                assert_eq!(compare(a, b), i.cmp(&j), "rank {i} vs {j}");
967            }
968        }
969    }
970
971    #[test]
972    fn number_order_across_int_and_float() {
973        assert_eq!(compare(&json!(1), &json!(1.0)), Ordering::Equal);
974        assert!(values_equal(&json!(1), &json!(1.0)));
975        assert_eq!(compare(&json!(2), &json!(1.5)), Ordering::Greater);
976        assert_eq!(compare(&json!(-1), &json!(0)), Ordering::Less);
977        // 扩展 Long 与普通数字同序
978        assert!(values_equal(&json!({ "$numberLong": "3" }), &json!(3)));
979    }
980
981    #[test]
982    fn compare_object_and_array() {
983        assert_eq!(
984            compare(&json!({ "a": 1 }), &json!({ "a": 2 })),
985            Ordering::Less
986        );
987        // 前缀相等 → 短者小
988        assert_eq!(
989            compare(&json!({ "a": 2 }), &json!({ "a": 2, "b": 1 })),
990            Ordering::Less
991        );
992        assert_eq!(compare(&json!([1, 2]), &json!([1, 2, 3])), Ordering::Less);
993        assert_eq!(compare(&json!([1, 3]), &json!([1, 2])), Ordering::Greater);
994    }
995
996    #[test]
997    fn array_eq_hit_helper() {
998        // 数组字段等值命中(Mongo 语义):候选为数组,元素命中即真
999        assert!(matches_value(&json!(["x", "y"]), &json!("x")));
1000        assert!(matches_value(&json!("x"), &json!("x")));
1001        assert!(!matches_value(&json!(["y"]), &json!("x")));
1002    }
1003
1004    #[test]
1005    fn eval_expr_minimal() {
1006        let doc = json!({ "x": 1, "y": 2 });
1007        let vars = Map::new();
1008        assert_eq!(
1009            eval_expr(&json!({ "$eq": ["$x", 1] }), &doc, &vars).unwrap(),
1010            json!(true)
1011        );
1012        assert_eq!(
1013            eval_expr(&json!({ "$eq": ["$x", "$y"] }), &doc, &vars).unwrap(),
1014            json!(false)
1015        );
1016        assert_eq!(
1017            eval_expr(
1018                &json!({ "$and": [ { "$eq": ["$x", 1] }, { "$eq": ["$y", 2] } ] }),
1019                &doc,
1020                &vars
1021            )
1022            .unwrap(),
1023            json!(true)
1024        );
1025        // $ifNull:缺失字段 → 回退值
1026        assert_eq!(
1027            eval_expr(&json!({ "$ifNull": ["$missing", "d"] }), &doc, &vars).unwrap(),
1028            json!("d")
1029        );
1030        // $$var 引用
1031        let mut vars2 = Map::new();
1032        vars2.insert("v".to_string(), json!(42));
1033        assert_eq!(eval_expr(&json!("$$v"), &doc, &vars2).unwrap(), json!(42));
1034        assert_eq!(
1035            eval_expr(&json!({ "$eq": ["$x", "$$v"] }), &doc, &vars2).unwrap(),
1036            json!(false)
1037        );
1038        // 未知算子 → Err(禁静默)
1039        assert!(eval_expr(&json!({ "$mul": [1, 2] }), &doc, &vars).is_err());
1040    }
1041
1042    #[test]
1043    fn expr_size() {
1044        let vars = Map::new();
1045        let doc = json!({ "arr": [1, 2, 3], "empty": [], "n": 5 });
1046        assert_eq!(
1047            eval_expr(&json!({ "$size": "$arr" }), &doc, &vars).unwrap(),
1048            json!(3)
1049        );
1050        assert_eq!(
1051            eval_expr(&json!({ "$size": "$empty" }), &doc, &vars).unwrap(),
1052            json!(0)
1053        );
1054        // 非数组 → Err(Mongo 同样抛错,禁静默)
1055        assert!(eval_expr(&json!({ "$size": "$n" }), &doc, &vars).is_err());
1056        assert!(eval_expr(&json!({ "$size": "$missing" }), &doc, &vars).is_err());
1057    }
1058
1059    #[test]
1060    fn expr_sum_avg_min_max_array_form() {
1061        let vars = Map::new();
1062        let doc = json!({
1063            "scores": [3, 7, 5],
1064            "mixed": [1, "x", 4, null],
1065            "empty": [],
1066            "gone": null,
1067            "one": 9
1068        });
1069        // $sum:整数值 → 整数形态(对齐 pipeline.rs::accumulate 的 number_of)
1070        assert_eq!(
1071            eval_expr(&json!({ "$sum": "$scores" }), &doc, &vars).unwrap(),
1072            json!(15)
1073        );
1074        // 忽略非数值与 null;空数组 → 0;缺失/null → 0
1075        assert_eq!(
1076            eval_expr(&json!({ "$sum": "$mixed" }), &doc, &vars).unwrap(),
1077            json!(5)
1078        );
1079        assert_eq!(
1080            eval_expr(&json!({ "$sum": "$empty" }), &doc, &vars).unwrap(),
1081            json!(0)
1082        );
1083        assert_eq!(
1084            eval_expr(&json!({ "$sum": "$gone" }), &doc, &vars).unwrap(),
1085            json!(0)
1086        );
1087        assert_eq!(
1088            eval_expr(&json!({ "$sum": "$missing" }), &doc, &vars).unwrap(),
1089            json!(0)
1090        );
1091        // $avg:恒浮点;空集/无可用值 → null
1092        assert_eq!(
1093            eval_expr(&json!({ "$avg": "$scores" }), &doc, &vars).unwrap(),
1094            json!(5.0)
1095        );
1096        assert_eq!(
1097            eval_expr(&json!({ "$avg": "$empty" }), &doc, &vars).unwrap(),
1098            Value::Null
1099        );
1100        assert_eq!(
1101            eval_expr(&json!({ "$avg": "$missing" }), &doc, &vars).unwrap(),
1102            Value::Null
1103        );
1104        // $min / $max:BSON 序极值,忽略 null;空集 → null
1105        assert_eq!(
1106            eval_expr(&json!({ "$min": "$scores" }), &doc, &vars).unwrap(),
1107            json!(3)
1108        );
1109        assert_eq!(
1110            eval_expr(&json!({ "$max": "$scores" }), &doc, &vars).unwrap(),
1111            json!(7)
1112        );
1113        assert_eq!(
1114            eval_expr(&json!({ "$min": "$mixed" }), &doc, &vars).unwrap(),
1115            json!(1)
1116        );
1117        assert_eq!(
1118            eval_expr(&json!({ "$min": "$empty" }), &doc, &vars).unwrap(),
1119            Value::Null
1120        );
1121        // 单值形态 = 单元素数组($sum(9) → 9 整数;$avg(9) → 9.0 浮点)
1122        assert_eq!(
1123            eval_expr(&json!({ "$sum": "$one" }), &doc, &vars).unwrap(),
1124            json!(9)
1125        );
1126        assert_eq!(
1127            eval_expr(&json!({ "$avg": "$one" }), &doc, &vars).unwrap(),
1128            json!(9.0)
1129        );
1130        assert_eq!(
1131            eval_expr(&json!({ "$min": "$one" }), &doc, &vars).unwrap(),
1132            json!(9)
1133        );
1134    }
1135
1136    #[test]
1137    fn projection_include_nested() {
1138        let doc = json!({ "_id": 7, "a": { "b": 1, "c": 2 }, "d": 3 });
1139        // 嵌套包含:仅保留 a.b
1140        assert_eq!(
1141            apply_projection(&doc, &json!({ "a.b": 1 })).unwrap(),
1142            json!({ "_id": 7, "a": { "b": 1 } })
1143        );
1144        // 包含式默认带 _id
1145        assert_eq!(
1146            apply_projection(&doc, &json!({ "d": 1 })).unwrap(),
1147            json!({ "_id": 7, "d": 3 })
1148        );
1149        // _id:0 与包含式混用
1150        assert_eq!(
1151            apply_projection(&doc, &json!({ "d": 1, "_id": 0 })).unwrap(),
1152            json!({ "d": 3 })
1153        );
1154    }
1155
1156    #[test]
1157    fn projection_exclude_and_errors() {
1158        let doc = json!({ "_id": 7, "a": { "b": 1, "c": 2 }, "d": 3 });
1159        // 嵌套排除
1160        assert_eq!(
1161            apply_projection(&doc, &json!({ "a.b": 0 })).unwrap(),
1162            json!({ "_id": 7, "a": { "c": 2 }, "d": 3 })
1163        );
1164        // 仅 _id:0 → 去掉 _id,其余保留
1165        assert_eq!(
1166            apply_projection(&doc, &json!({ "_id": 0 })).unwrap(),
1167            json!({ "a": { "b": 1, "c": 2 }, "d": 3 })
1168        );
1169        // 非 _id 的包含/排除混用 → Err
1170        assert!(apply_projection(&doc, &json!({ "a": 1, "d": 0 })).is_err());
1171        // 非法投影值 → Err
1172        assert!(apply_projection(&doc, &json!({ "a": 2 })).is_err());
1173    }
1174
1175    /// 04 inline-array agg 新增算子:$ne / $filter / $setUnion,
1176    /// 以及 $countDistinct 编译形态端到端(与 lookup.rs inline 分支生成形态同款)
1177    #[test]
1178    fn ne_filter_set_union_eval() {
1179        let vars = Map::new();
1180        let empty = json!({});
1181        // $ne:不等 → true;null == null → false(剔 null 过滤的语义根基)
1182        assert_eq!(
1183            eval_expr(&json!({ "$ne": [1, 2] }), &empty, &vars).unwrap(),
1184            json!(true)
1185        );
1186        assert_eq!(
1187            eval_expr(&json!({ "$ne": [null, null] }), &empty, &vars).unwrap(),
1188            json!(false)
1189        );
1190
1191        // $filter:as 变量绑定 + cond 逐元素过滤(剔 null)
1192        let doc = json!({ "arr": ["a", null, "b", null] });
1193        assert_eq!(
1194            eval_expr(
1195                &json!({
1196                    "$filter": {
1197                        "input": "$arr",
1198                        "as": "v",
1199                        "cond": { "$ne": ["$$v", null] }
1200                    }
1201                }),
1202                &doc,
1203                &vars
1204            )
1205            .unwrap(),
1206            json!(["a", "b"])
1207        );
1208
1209        // $setUnion:按序并集 + 线性去重
1210        assert_eq!(
1211            eval_expr(
1212                &json!({ "$setUnion": [["a", "b"], ["b", "c"], []] }),
1213                &empty,
1214                &vars
1215            )
1216            .unwrap(),
1217            json!(["a", "b", "c"])
1218        );
1219
1220        // $countDistinct 编译形态端到端:去重且剔 null;空数组 / 缺字段 → 0
1221        let shape = |path: &str| {
1222            json!({
1223                "$size": {
1224                    "$setUnion": [
1225                        {
1226                            "$filter": {
1227                                "input": { "$ifNull": [path, []] },
1228                                "as": "v",
1229                                "cond": { "$ne": ["$$v", null] }
1230                            }
1231                        },
1232                        []
1233                    ]
1234                }
1235            })
1236        };
1237        let doc = json!({ "tags": ["x", "y", "x", null] });
1238        assert_eq!(eval_expr(&shape("$tags"), &doc, &vars).unwrap(), json!(2));
1239        let no_elems = json!({ "tags": [] });
1240        assert_eq!(
1241            eval_expr(&shape("$tags"), &no_elems, &vars).unwrap(),
1242            json!(0)
1243        );
1244        let missing = json!({});
1245        assert_eq!(
1246            eval_expr(&shape("$tags"), &missing, &vars).unwrap(),
1247            json!(0)
1248        );
1249    }
1250
1251    /// 点路径数组展开(eval_field_path):`$arr.f` 表达式引用扇出取元素列;
1252    /// 关系聚合计算列(`$sum:"$as.f"`)与 inline-array agg(`$max:"$bidders.amount"`)
1253    /// 共用此语义(漉金Core 接入扩展 04 修复)
1254    #[test]
1255    fn field_path_array_fanout_eval() {
1256        let vars = Map::new();
1257        // 对象数组点路径 → 元素列(缺失子路径的元素跳过);$max/$sum 在元素列上聚合
1258        let doc = json!({ "bidders": [
1259            { "bidderId": "u1", "amount": 100 },
1260            { "bidderId": "u2" },
1261            { "bidderId": "u1", "amount": 300 }
1262        ] });
1263        assert_eq!(
1264            eval_expr(&json!({ "$max": "$bidders.amount" }), &doc, &vars).unwrap(),
1265            json!(300)
1266        );
1267        assert_eq!(
1268            eval_expr(&json!({ "$sum": "$bidders.amount" }), &doc, &vars).unwrap(),
1269            json!(400)
1270        );
1271        // 扇出形态:缺子路径元素不产出 → 元素列 [ "u1", "u2", "u1" ]
1272        assert_eq!(
1273            eval_expr(
1274                &json!({ "$filter": {
1275                "input": { "$ifNull": ["$bidders.bidderId", []] },
1276                "as": "v",
1277                "cond": { "$ne": ["$$v", null] }
1278            } }),
1279                &doc,
1280                &vars
1281            )
1282            .unwrap(),
1283            json!(["u1", "u2", "u1"])
1284        );
1285        // 空数组:扇出产出空数组(非 missing;$ifNull 不触发兜底)
1286        let empty = json!({ "bidders": [] });
1287        assert_eq!(
1288            eval_expr(&json!("$bidders.amount"), &empty, &vars).unwrap(),
1289            json!([])
1290        );
1291        // 缺字段:无扇出 → Null($ifNull 兜底语义依赖该形态)
1292        let missing = json!({});
1293        assert_eq!(
1294            eval_expr(&json!("$bidders.amount"), &missing, &vars).unwrap(),
1295            Value::Null
1296        );
1297        // 纯对象路径不受影响(标量/对象/数组整体原样)
1298        let plain = json!({ "a": { "b": [1, 2] } });
1299        assert_eq!(
1300            eval_expr(&json!("$a.b"), &plain, &vars).unwrap(),
1301            json!([1, 2])
1302        );
1303    }
1304}