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/// - 普通对象 `{k: <expr>, …}` 逐键求值构造新文档;普通数组逐元素求值。
330///
331/// 未知算子一律 `Err`(禁静默)。
332pub fn eval_expr(expr: &Value, doc: &Value, vars: &Map<String, Value>) -> Result<Value, String> {
333    match expr {
334        Value::String(s) => eval_ref(s, doc, vars),
335        Value::Array(a) => a
336            .iter()
337            .map(|e| eval_expr(e, doc, vars))
338            .collect::<Result<Vec<_>, _>>()
339            .map(Value::Array),
340        Value::Object(o) => {
341            // 扩展类型对象(`$oid` 等)原样返回,避免被误判为算子
342            if bson::is_extended(expr) {
343                return Ok(expr.clone());
344            }
345            if o.len() == 1 {
346                if let Some(op) = o.keys().next().map(String::as_str) {
347                    if let Some(stripped) = op.strip_prefix('$') {
348                        return eval_operator(
349                            stripped,
350                            o.values().next().expect("len=1"),
351                            doc,
352                            vars,
353                        );
354                    }
355                }
356            }
357            let mut out = Map::new();
358            for (k, v) in o {
359                out.insert(k.clone(), eval_expr(v, doc, vars)?);
360            }
361            Ok(Value::Object(out))
362        }
363        other => Ok(other.clone()),
364    }
365}
366
367fn eval_operator(
368    op: &str,
369    arg: &Value,
370    doc: &Value,
371    vars: &Map<String, Value>,
372) -> Result<Value, String> {
373    match op {
374        "eq" => {
375            let (a, b) = two_operands("$eq", arg)?;
376            Ok(Value::Bool(values_equal(
377                &eval_expr(a, doc, vars)?,
378                &eval_expr(b, doc, vars)?,
379            )))
380        }
381        "and" => {
382            for e in array_operands("$and", arg)? {
383                if !truthy(&eval_expr(e, doc, vars)?) {
384                    return Ok(Value::Bool(false));
385                }
386            }
387            Ok(Value::Bool(true))
388        }
389        "or" => {
390            for e in array_operands("$or", arg)? {
391                if truthy(&eval_expr(e, doc, vars)?) {
392                    return Ok(Value::Bool(true));
393                }
394            }
395            Ok(Value::Bool(false))
396        }
397        "ifNull" => {
398            let (a, b) = two_operands("$ifNull", arg)?;
399            let v = eval_expr(a, doc, vars)?;
400            if v.is_null() {
401                eval_expr(b, doc, vars)
402            } else {
403                Ok(v)
404            }
405        }
406        // `$cond`:core 的 `$lookup.let`(数组外键守卫)与 `$group` 的 `$count` 字段形态
407        // 均发射该算子,故必须支持;数组形态 `[if, then, else]` 与对象形态 `{if,then,else}` 均可。
408        "cond" => {
409            let (c, t, e) = cond_operands(arg)?;
410            if truthy(&eval_expr(c, doc, vars)?) {
411                eval_expr(t, doc, vars)
412            } else {
413                eval_expr(e, doc, vars)
414            }
415        }
416        "isArray" => Ok(Value::Bool(eval_expr(arg, doc, vars)?.is_array())),
417        // `$in`:core 数组外键关系发射 `{$expr: {$in: ["$fk", "$$let"]}}`
418        "in" => {
419            let (needle, hay) = two_operands("$in", arg)?;
420            let needle = eval_expr(needle, doc, vars)?;
421            let hay = eval_expr(hay, doc, vars)?;
422            let items = hay
423                .as_array()
424                .ok_or_else(|| "$in 的第二个操作数必须是数组".to_string())?;
425            let hit = match &needle {
426                Value::Array(a) => a
427                    .iter()
428                    .any(|n| items.iter().any(|c| values_equal(n, c))),
429                other => items.iter().any(|c| values_equal(other, c)),
430            };
431            Ok(Value::Bool(hit))
432        }
433        // `$arrayElemAt`:core 的全表单组空集护栏(`$facet` + `$replaceRoot`)使用
434        "arrayElemAt" => {
435            let (arr_e, idx_e) = two_operands("$arrayElemAt", arg)?;
436            let arr = eval_expr(arr_e, doc, vars)?;
437            let idx_v = eval_expr(idx_e, doc, vars)?;
438            let idx = idx_v
439                .as_i64()
440                .ok_or_else(|| "$arrayElemAt 的下标必须是整数".to_string())?;
441            let items = arr
442                .as_array()
443                .ok_or_else(|| "$arrayElemAt 的第一个操作数必须是数组".to_string())?;
444            let len = items.len() as i64;
445            let i = if idx < 0 { len + idx } else { idx };
446            // 越界 → `null`(Mongo 为 missing,在 `$ifNull` 兜底语境下等价)
447            Ok(items.get(i as usize).cloned().unwrap_or(Value::Null))
448        }
449        // `$size`:数组长度(core 关系聚合计算列 `$count` 发射 `{$size: {$ifNull: [..., []]}}`)
450        "size" => {
451            let arr = eval_expr(arg, doc, vars)?;
452            let items = arr
453                .as_array()
454                .ok_or_else(|| format!("$size 的操作数必须是数组,收到 {}", value_type_name(&arr)))?;
455            Ok(Value::from(items.len() as i64))
456        }
457        // `$sum` / `$avg` / `$min` / `$max`:数组聚合表达式形态(core 关系聚合计算列
458        // `$addFields` 发射 `{$sum/$avg/$min/$max: "$as.field"}`,`as` 为 `$lookup` 结果数组)。
459        // 语义对齐 `pipeline.rs::accumulate`($sum 忽略非数值、整数值→整数形态;$avg 恒浮点;
460        // $min/$max 按 BSON 序、忽略 null;空集 → $sum=0 / 其余 null)。单值形态 = 单元素数组。
461        "sum" | "avg" | "min" | "max" => {
462            let v = eval_expr(arg, doc, vars)?;
463            match v {
464                Value::Array(items) => accumulate_expr(op, &items),
465                other => accumulate_expr(op, std::slice::from_ref(&other)),
466            }
467        }
468        other => Err(format!(
469            "不支持的聚合表达式算子: ${other}(本地求值器仅支持 $eq/$and/$or/$ifNull/$cond/$isArray/$in/$arrayElemAt/$size/$sum/$avg/$min/$max,拒绝静默)"
470        )),
471    }
472}
473
474/// 数组聚合(`$sum` / `$avg` / `$min` / `$max` 的表达式形态)——语义镜像
475/// [`super::pipeline::accumulate`],保证「关系聚合计算列」与「根级 `$group`」数值形态一致。
476fn accumulate_expr(op: &str, items: &[Value]) -> Result<Value, String> {
477    match op {
478        "sum" => {
479            let mut sum = 0.0_f64;
480            for v in items {
481                if let Some(n) = as_number(v) {
482                    sum += n;
483                }
484            }
485            Ok(number_of(sum))
486        }
487        "avg" => {
488            let (mut sum, mut count) = (0.0_f64, 0_usize);
489            for v in items {
490                if let Some(n) = as_number(v) {
491                    sum += n;
492                    count += 1;
493                }
494            }
495            Ok(if count == 0 {
496                Value::Null
497            } else {
498                float_of(sum / count as f64)
499            })
500        }
501        _ => {
502            let mut best: Option<&Value> = None;
503            for v in items {
504                if v.is_null() {
505                    continue;
506                }
507                best = Some(match best {
508                    None => v,
509                    Some(cur) => {
510                        let take = if op == "min" {
511                            compare(v, cur) == Ordering::Less
512                        } else {
513                            compare(v, cur) == Ordering::Greater
514                        };
515                        if take {
516                            v
517                        } else {
518                            cur
519                        }
520                    }
521                });
522            }
523            Ok(best.cloned().unwrap_or(Value::Null))
524        }
525    }
526}
527
528fn value_type_name(v: &Value) -> &'static str {
529    match v {
530        Value::Null => "null",
531        Value::Bool(_) => "bool",
532        Value::Number(_) => "number",
533        Value::String(_) => "string",
534        Value::Array(_) => "array",
535        Value::Object(_) => "object",
536    }
537}
538
539fn two_operands<'a>(name: &str, arg: &'a Value) -> Result<(&'a Value, &'a Value), String> {
540    let arr = arg
541        .as_array()
542        .ok_or_else(|| format!("{name} 需要操作数数组"))?;
543    if arr.len() != 2 {
544        return Err(format!("{name} 需要恰好两个操作数,收到 {}", arr.len()));
545    }
546    Ok((&arr[0], &arr[1]))
547}
548
549fn array_operands<'a>(name: &str, arg: &'a Value) -> Result<&'a [Value], String> {
550    arg.as_array()
551        .map(Vec::as_slice)
552        .ok_or_else(|| format!("{name} 需要操作数数组"))
553}
554
555/// `$cond` 操作数:数组形态 `[if, then, else]` 或对象形态 `{if, then, else}`。
556fn cond_operands(arg: &Value) -> Result<(&Value, &Value, &Value), String> {
557    if let Some(a) = arg.as_array() {
558        if a.len() != 3 {
559            return Err(format!("$cond 数组形态需要 3 个操作数,收到 {}", a.len()));
560        }
561        return Ok((&a[0], &a[1], &a[2]));
562    }
563    if let Some(o) = arg.as_object() {
564        let pick = |k: &str| {
565            o.get(k)
566                .ok_or_else(|| format!("$cond 对象形态缺少 \"{k}\""))
567        };
568        return Ok((pick("if")?, pick("then")?, pick("else")?));
569    }
570    Err("$cond 需要数组 [if, then, else] 或对象 {if, then, else}".to_string())
571}
572
573/// Mongo 聚合布尔真值:`false` / `null` / `0` 为假,其余为真。
574fn truthy(v: &Value) -> bool {
575    match v {
576        Value::Null => false,
577        Value::Bool(b) => *b,
578        Value::Number(n) => n.as_f64().map(|x| x != 0.0).unwrap_or(true),
579        _ => true,
580    }
581}
582
583/// `"$field"` / `"$$var"` 引用求值;非 `$` 前缀的字符串为字面量。
584fn eval_ref(s: &str, doc: &Value, vars: &Map<String, Value>) -> Result<Value, String> {
585    if let Some(rest) = s.strip_prefix("$$") {
586        let (name, path) = match rest.split_once('.') {
587            Some((n, p)) => (n, p),
588            None => (rest, ""),
589        };
590        let base = vars.get(name).cloned().unwrap_or(Value::Null);
591        Ok(reference_path(&base, path))
592    } else if let Some(rest) = s.strip_prefix('$') {
593        Ok(get_path(doc, rest).cloned().unwrap_or(Value::Null))
594    } else {
595        Ok(Value::String(s.to_string()))
596    }
597}
598
599fn reference_path(base: &Value, path: &str) -> Value {
600    if path.is_empty() {
601        base.clone()
602    } else {
603        get_path(base, path).cloned().unwrap_or(Value::Null)
604    }
605}
606
607// ---------------------------------------------------------------------------
608// 投影(包含 / 排除;`_id` 可与包含式混用)
609// ---------------------------------------------------------------------------
610
611/// 应用投影:包含式 / 排除式;`_id` 特殊处理(包含式默认带 `_id`,`_id: 0` 可与之混用)。
612///
613/// 空投影对象 → 原样返回(视为无投影)。同时含非 `_id` 的包含与排除 → `Err`(禁静默)。
614pub fn apply_projection(doc: &Value, projection: &Value) -> Result<Value, String> {
615    let obj = projection
616        .as_object()
617        .ok_or_else(|| format!("投影必须是对象,收到 {}", type_name(projection)))?;
618    if obj.is_empty() {
619        return Ok(doc.clone());
620    }
621
622    let mut include_paths: Vec<&String> = Vec::new();
623    let mut exclude_paths: Vec<&String> = Vec::new();
624    let mut id: Option<bool> = None;
625    for (k, v) in obj {
626        let flag = projection_flag(v)?;
627        if k == "_id" {
628            id = Some(flag);
629        } else if flag {
630            include_paths.push(k);
631        } else {
632            exclude_paths.push(k);
633        }
634    }
635    if !include_paths.is_empty() && !exclude_paths.is_empty() {
636        return Err("投影不允许同时包含与排除字段(`_id` 除外)".to_string());
637    }
638
639    let include_mode = if !include_paths.is_empty() {
640        true
641    } else if !exclude_paths.is_empty() {
642        false
643    } else {
644        // 仅出现 `_id` 指令:`_id:1` → 只保留 `_id`;`_id:0` → 全保留除 `_id`
645        id == Some(true)
646    };
647
648    if include_mode {
649        let mut out = Map::new();
650        if id != Some(false) {
651            if let Some(v) = doc.get("_id") {
652                out.insert("_id".to_string(), v.clone());
653            }
654        }
655        for path in &include_paths {
656            if let Some(v) = project_included(doc, &segments(path)) {
657                merge_into(&mut out, v);
658            }
659        }
660        Ok(Value::Object(out))
661    } else {
662        let mut out = doc.clone();
663        for path in &exclude_paths {
664            remove_segments(&mut out, &segments(path));
665        }
666        if id == Some(false) {
667            if let Value::Object(m) = &mut out {
668                m.remove("_id");
669            }
670        }
671        Ok(out)
672    }
673}
674
675/// 投影值 → 包含(bool=true) / 排除(bool=false);仅接受 `0/1` 与 `false/true`。
676fn projection_flag(v: &Value) -> Result<bool, String> {
677    match v {
678        Value::Bool(b) => Ok(*b),
679        Value::Number(n) => match (n.as_i64(), n.as_f64()) {
680            (Some(0), _) | (_, Some(0.0)) => Ok(false),
681            (Some(1), _) | (_, Some(1.0)) => Ok(true),
682            _ => Err(format!("投影值必须是 0/1 或 false/true,收到 {n}")),
683        },
684        other => Err(format!(
685            "不支持的投影形态: {other}(本地求值器仅支持包含/排除,拒绝静默)"
686        )),
687    }
688}
689
690/// 按剩余段对 `doc` 做包含投影,返回投影后的子树;路径不存在 → `None`。
691/// 遇数组:逐元素按同段继续(Mongo 数组映射语义的简化),无命中项跳过。
692fn project_included(doc: &Value, segs: &[&str]) -> Option<Value> {
693    if segs.is_empty() {
694        return Some(doc.clone());
695    }
696    match doc {
697        Value::Object(o) => {
698            let child = o.get(segs[0])?;
699            let sub = project_included(child, &segs[1..])?;
700            let mut m = Map::new();
701            m.insert(segs[0].to_string(), sub);
702            Some(Value::Object(m))
703        }
704        Value::Array(a) => {
705            let mut arr = Vec::with_capacity(a.len());
706            for el in a {
707                if let Some(v) = project_included(el, segs) {
708                    arr.push(v);
709                }
710            }
711            Some(Value::Array(arr))
712        }
713        _ => None,
714    }
715}
716
717/// 包含式取字段(点号路径,嵌套还原);路径不存在 → `None`。
718///
719/// 供聚合投影(`$project` 含表达式值)按路径并入输出文档使用。
720pub fn project_include(doc: &Value, path: &str) -> Option<Value> {
721    project_included(doc, &segments(path))
722}
723
724/// 把投影子树并入输出文档;同名对象递归合并,其余覆盖。
725pub fn merge_into(dst: &mut Map<String, Value>, src: Value) {
726    let Value::Object(src) = src else {
727        return;
728    };
729    for (k, v) in src {
730        let nested = matches!(dst.get(&k), Some(Value::Object(_))) && v.is_object();
731        if nested {
732            if let Some(Value::Object(d)) = dst.get_mut(&k) {
733                merge_into(d, v);
734            }
735        } else {
736            dst.insert(k, v);
737        }
738    }
739}
740
741/// 按段做排除投影(原地);遇数组对每个元素继续。
742pub fn remove_path(doc: &mut Value, path: &str) {
743    remove_segments(doc, &segments(path));
744}
745
746/// 按段路径写入值(`$addFields` / `$unwind` 用):中间层不存在或非对象时补建为对象。
747pub fn set_path(doc: &mut Value, path: &str, val: Value) {
748    set_segments(doc, &segments(path), val);
749}
750
751fn set_segments(cur: &mut Value, segs: &[&str], val: Value) {
752    if segs.is_empty() {
753        *cur = val;
754        return;
755    }
756    if !cur.is_object() {
757        *cur = Value::Object(Map::new());
758    }
759    let Value::Object(o) = cur else { return };
760    if segs.len() == 1 {
761        o.insert(segs[0].to_string(), val);
762        return;
763    }
764    let entry = o
765        .entry(segs[0].to_string())
766        .or_insert_with(|| Value::Object(Map::new()));
767    set_segments(entry, &segs[1..], val);
768}
769
770fn remove_segments(doc: &mut Value, segs: &[&str]) {
771    if segs.is_empty() {
772        return;
773    }
774    match doc {
775        Value::Object(o) => {
776            if segs.len() == 1 {
777                o.remove(segs[0]);
778            } else if let Some(v) = o.get_mut(segs[0]) {
779                remove_segments(v, &segs[1..]);
780            }
781        }
782        Value::Array(a) => {
783            for el in a {
784                remove_segments(el, segs);
785            }
786        }
787        _ => {}
788    }
789}
790
791fn type_name(v: &Value) -> &'static str {
792    match v {
793        Value::Null => "null",
794        Value::Bool(_) => "bool",
795        Value::Number(_) => "number",
796        Value::String(_) => "string",
797        Value::Array(_) => "array",
798        Value::Object(_) => "object",
799    }
800}
801
802#[cfg(test)]
803mod tests {
804    use super::*;
805    use serde_json::json;
806
807    #[test]
808    fn max_docs_guard_constant() {
809        assert_eq!(MAX_LOCAL_COLLECTION_DOCS, 100_000);
810    }
811
812    #[test]
813    fn get_path_three_states() {
814        let doc = json!({ "a": null, "b": { "c": 1 } });
815        // 显式 null:Some(Null);缺失:None
816        assert_eq!(get_path(&doc, "a"), Some(&Value::Null));
817        assert_eq!(get_path(&doc, "missing"), None);
818        assert_eq!(get_path(&doc, "b.missing"), None);
819        assert_eq!(get_path(&doc, "b.c"), Some(&json!(1)));
820        // 严格取值不下钻数组
821        let arr = json!({ "a": [ { "b": 1 } ] });
822        assert_eq!(get_path(&arr, "a.b"), None);
823    }
824
825    #[test]
826    fn candidates_three_states_and_array_expansion() {
827        let doc = json!({ "a": null, "arr": [ { "b": 1 }, { "b": 2 } ] });
828        // 缺失 → 空;显式 null → [Null]
829        assert!(resolve_path_candidates(&doc, "missing").is_empty());
830        assert_eq!(resolve_path_candidates(&doc, "a"), vec![&Value::Null]);
831        // 数组下钻:a.b 命中每个元素
832        assert_eq!(
833            resolve_path_candidates(&doc, "arr.b"),
834            vec![&json!(1), &json!(2)]
835        );
836        // 末段为数组字段:候选为数组整体
837        let tags = json!({ "tags": ["x", "y"] });
838        assert_eq!(
839            resolve_path_candidates(&tags, "tags"),
840            vec![&json!(["x", "y"])]
841        );
842    }
843
844    #[test]
845    fn bson_type_order_across_types() {
846        let min = json!({ "$minKey": 1 });
847        let null = Value::Null;
848        let num = json!(5);
849        let s = json!("abc");
850        let obj = json!({ "x": 1 });
851        let arr = json!([1, 2]);
852        let boolean = json!(true);
853        let date = json!({ "$date": 0 });
854        let max = json!({ "$maxKey": 1 });
855        let seq = [&min, &null, &num, &s, &obj, &arr, &boolean, &date, &max];
856        for (i, a) in seq.iter().enumerate() {
857            for (j, b) in seq.iter().enumerate() {
858                assert_eq!(compare(a, b), i.cmp(&j), "rank {i} vs {j}");
859            }
860        }
861    }
862
863    #[test]
864    fn number_order_across_int_and_float() {
865        assert_eq!(compare(&json!(1), &json!(1.0)), Ordering::Equal);
866        assert!(values_equal(&json!(1), &json!(1.0)));
867        assert_eq!(compare(&json!(2), &json!(1.5)), Ordering::Greater);
868        assert_eq!(compare(&json!(-1), &json!(0)), Ordering::Less);
869        // 扩展 Long 与普通数字同序
870        assert!(values_equal(&json!({ "$numberLong": "3" }), &json!(3)));
871    }
872
873    #[test]
874    fn compare_object_and_array() {
875        assert_eq!(
876            compare(&json!({ "a": 1 }), &json!({ "a": 2 })),
877            Ordering::Less
878        );
879        // 前缀相等 → 短者小
880        assert_eq!(
881            compare(&json!({ "a": 2 }), &json!({ "a": 2, "b": 1 })),
882            Ordering::Less
883        );
884        assert_eq!(compare(&json!([1, 2]), &json!([1, 2, 3])), Ordering::Less);
885        assert_eq!(compare(&json!([1, 3]), &json!([1, 2])), Ordering::Greater);
886    }
887
888    #[test]
889    fn array_eq_hit_helper() {
890        // 数组字段等值命中(Mongo 语义):候选为数组,元素命中即真
891        assert!(matches_value(&json!(["x", "y"]), &json!("x")));
892        assert!(matches_value(&json!("x"), &json!("x")));
893        assert!(!matches_value(&json!(["y"]), &json!("x")));
894    }
895
896    #[test]
897    fn eval_expr_minimal() {
898        let doc = json!({ "x": 1, "y": 2 });
899        let vars = Map::new();
900        assert_eq!(
901            eval_expr(&json!({ "$eq": ["$x", 1] }), &doc, &vars).unwrap(),
902            json!(true)
903        );
904        assert_eq!(
905            eval_expr(&json!({ "$eq": ["$x", "$y"] }), &doc, &vars).unwrap(),
906            json!(false)
907        );
908        assert_eq!(
909            eval_expr(
910                &json!({ "$and": [ { "$eq": ["$x", 1] }, { "$eq": ["$y", 2] } ] }),
911                &doc,
912                &vars
913            )
914            .unwrap(),
915            json!(true)
916        );
917        // $ifNull:缺失字段 → 回退值
918        assert_eq!(
919            eval_expr(&json!({ "$ifNull": ["$missing", "d"] }), &doc, &vars).unwrap(),
920            json!("d")
921        );
922        // $$var 引用
923        let mut vars2 = Map::new();
924        vars2.insert("v".to_string(), json!(42));
925        assert_eq!(eval_expr(&json!("$$v"), &doc, &vars2).unwrap(), json!(42));
926        assert_eq!(
927            eval_expr(&json!({ "$eq": ["$x", "$$v"] }), &doc, &vars2).unwrap(),
928            json!(false)
929        );
930        // 未知算子 → Err(禁静默)
931        assert!(eval_expr(&json!({ "$mul": [1, 2] }), &doc, &vars).is_err());
932    }
933
934    #[test]
935    fn expr_size() {
936        let vars = Map::new();
937        let doc = json!({ "arr": [1, 2, 3], "empty": [], "n": 5 });
938        assert_eq!(
939            eval_expr(&json!({ "$size": "$arr" }), &doc, &vars).unwrap(),
940            json!(3)
941        );
942        assert_eq!(
943            eval_expr(&json!({ "$size": "$empty" }), &doc, &vars).unwrap(),
944            json!(0)
945        );
946        // 非数组 → Err(Mongo 同样抛错,禁静默)
947        assert!(eval_expr(&json!({ "$size": "$n" }), &doc, &vars).is_err());
948        assert!(eval_expr(&json!({ "$size": "$missing" }), &doc, &vars).is_err());
949    }
950
951    #[test]
952    fn expr_sum_avg_min_max_array_form() {
953        let vars = Map::new();
954        let doc = json!({
955            "scores": [3, 7, 5],
956            "mixed": [1, "x", 4, null],
957            "empty": [],
958            "gone": null,
959            "one": 9
960        });
961        // $sum:整数值 → 整数形态(对齐 pipeline.rs::accumulate 的 number_of)
962        assert_eq!(
963            eval_expr(&json!({ "$sum": "$scores" }), &doc, &vars).unwrap(),
964            json!(15)
965        );
966        // 忽略非数值与 null;空数组 → 0;缺失/null → 0
967        assert_eq!(
968            eval_expr(&json!({ "$sum": "$mixed" }), &doc, &vars).unwrap(),
969            json!(5)
970        );
971        assert_eq!(
972            eval_expr(&json!({ "$sum": "$empty" }), &doc, &vars).unwrap(),
973            json!(0)
974        );
975        assert_eq!(
976            eval_expr(&json!({ "$sum": "$gone" }), &doc, &vars).unwrap(),
977            json!(0)
978        );
979        assert_eq!(
980            eval_expr(&json!({ "$sum": "$missing" }), &doc, &vars).unwrap(),
981            json!(0)
982        );
983        // $avg:恒浮点;空集/无可用值 → null
984        assert_eq!(
985            eval_expr(&json!({ "$avg": "$scores" }), &doc, &vars).unwrap(),
986            json!(5.0)
987        );
988        assert_eq!(
989            eval_expr(&json!({ "$avg": "$empty" }), &doc, &vars).unwrap(),
990            Value::Null
991        );
992        assert_eq!(
993            eval_expr(&json!({ "$avg": "$missing" }), &doc, &vars).unwrap(),
994            Value::Null
995        );
996        // $min / $max:BSON 序极值,忽略 null;空集 → null
997        assert_eq!(
998            eval_expr(&json!({ "$min": "$scores" }), &doc, &vars).unwrap(),
999            json!(3)
1000        );
1001        assert_eq!(
1002            eval_expr(&json!({ "$max": "$scores" }), &doc, &vars).unwrap(),
1003            json!(7)
1004        );
1005        assert_eq!(
1006            eval_expr(&json!({ "$min": "$mixed" }), &doc, &vars).unwrap(),
1007            json!(1)
1008        );
1009        assert_eq!(
1010            eval_expr(&json!({ "$min": "$empty" }), &doc, &vars).unwrap(),
1011            Value::Null
1012        );
1013        // 单值形态 = 单元素数组($sum(9) → 9 整数;$avg(9) → 9.0 浮点)
1014        assert_eq!(
1015            eval_expr(&json!({ "$sum": "$one" }), &doc, &vars).unwrap(),
1016            json!(9)
1017        );
1018        assert_eq!(
1019            eval_expr(&json!({ "$avg": "$one" }), &doc, &vars).unwrap(),
1020            json!(9.0)
1021        );
1022        assert_eq!(
1023            eval_expr(&json!({ "$min": "$one" }), &doc, &vars).unwrap(),
1024            json!(9)
1025        );
1026    }
1027
1028    #[test]
1029    fn projection_include_nested() {
1030        let doc = json!({ "_id": 7, "a": { "b": 1, "c": 2 }, "d": 3 });
1031        // 嵌套包含:仅保留 a.b
1032        assert_eq!(
1033            apply_projection(&doc, &json!({ "a.b": 1 })).unwrap(),
1034            json!({ "_id": 7, "a": { "b": 1 } })
1035        );
1036        // 包含式默认带 _id
1037        assert_eq!(
1038            apply_projection(&doc, &json!({ "d": 1 })).unwrap(),
1039            json!({ "_id": 7, "d": 3 })
1040        );
1041        // _id:0 与包含式混用
1042        assert_eq!(
1043            apply_projection(&doc, &json!({ "d": 1, "_id": 0 })).unwrap(),
1044            json!({ "d": 3 })
1045        );
1046    }
1047
1048    #[test]
1049    fn projection_exclude_and_errors() {
1050        let doc = json!({ "_id": 7, "a": { "b": 1, "c": 2 }, "d": 3 });
1051        // 嵌套排除
1052        assert_eq!(
1053            apply_projection(&doc, &json!({ "a.b": 0 })).unwrap(),
1054            json!({ "_id": 7, "a": { "c": 2 }, "d": 3 })
1055        );
1056        // 仅 _id:0 → 去掉 _id,其余保留
1057        assert_eq!(
1058            apply_projection(&doc, &json!({ "_id": 0 })).unwrap(),
1059            json!({ "a": { "b": 1, "c": 2 }, "d": 3 })
1060        );
1061        // 非 _id 的包含/排除混用 → Err
1062        assert!(apply_projection(&doc, &json!({ "a": 1, "d": 0 })).is_err());
1063        // 非法投影值 → Err
1064        assert!(apply_projection(&doc, &json!({ "a": 2 })).is_err());
1065    }
1066}