Skip to main content

rust_store_core/computes/
inject.rs

1//! asyncFn depends 注入 / 剔除(查询前注入 GQL 片段,查询后裁掉注入字段)
2
3use std::collections::HashMap;
4
5use serde_json::{Map, Value};
6
7use crate::pipeline::{parse, tokenize, RelAst};
8use crate::schema::Schema;
9
10use super::cache::ensure_cache;
11
12/// 单个关系的依赖字段需求(保序)
13#[derive(Debug, Clone, PartialEq)]
14pub struct RelDep {
15    pub name: String,
16    /// 需要的字段;空表示只依赖关系本身(注入整个关系)
17    pub fields: Vec<String>,
18}
19
20impl RelDep {
21    pub fn to_value(&self) -> Value {
22        Value::Object(Map::from_iter([
23            ("name".to_string(), Value::String(self.name.clone())),
24            (
25                "fields".to_string(),
26                Value::Array(self.fields.iter().cloned().map(Value::String).collect()),
27            ),
28        ]))
29    }
30}
31
32/// 注入信息:新增关系 → `All`(整条返回且需整体剔除);已有关系补齐字段 → `Fields`
33#[derive(Debug, Clone, PartialEq)]
34pub enum Injected {
35    All,
36    Fields(Vec<String>),
37}
38
39#[derive(Debug, Clone, PartialEq, Default)]
40pub struct InjectInfo {
41    pub relations: Vec<(String, Injected)>,
42    /// R7:asyncFn 计算列的**普通字段**依赖(非关系子字段),注入根 AST 后供 process_node
43    /// 保留、宿主 asyncFn 读取,随后在 strip 阶段从根文档剥离,避免泄漏到最终输出。
44    pub fields: Vec<String>,
45}
46
47impl InjectInfo {
48    pub fn is_empty(&self) -> bool {
49        self.relations.is_empty() && self.fields.is_empty()
50    }
51
52    /// 序列化为 JS 侧形状(Set → 数组,`'__all__'` → 字符串)
53    pub fn to_value(&self) -> Value {
54        let mut m = Map::new();
55        for (name, inj) in &self.relations {
56            let v = match inj {
57                Injected::All => Value::String("__all__".to_string()),
58                Injected::Fields(f) => Value::Array(f.iter().cloned().map(Value::String).collect()),
59            };
60            m.insert(name.clone(), v);
61        }
62        let mut root = Map::new();
63        if let Some(rm) = Value::Object(m).as_object() {
64            if !rm.is_empty() {
65                root.insert("relations".to_string(), Value::Object(rm.clone()));
66            }
67        }
68        if !self.fields.is_empty() {
69            root.insert(
70                "fields".to_string(),
71                Value::Array(self.fields.iter().cloned().map(Value::String).collect()),
72            );
73        }
74        Value::Object(Map::from_iter([(
75            "inject".to_string(),
76            Value::Object(root),
77        )]))
78    }
79
80    /// 从 [`to_value`] 产出的 JSON 重建(供 finalize_query 从 plan.postprocess 还原)
81    pub fn from_value(v: &Value) -> InjectInfo {
82        let mut relations = Vec::new();
83        let mut fields = Vec::new();
84        if let Some(Value::Object(m)) = v.get("inject").or(Some(v)) {
85            if let Some(Value::Object(rm)) = m.get("relations") {
86                for (name, inj) in rm {
87                    let parsed = match inj {
88                        Value::String(s) if s == "__all__" => Injected::All,
89                        Value::Array(arr) => Injected::Fields(
90                            arr.iter()
91                                .map(|f| f.as_str().unwrap_or_default().to_string())
92                                .collect(),
93                        ),
94                        _ => continue,
95                    };
96                    relations.push((name.clone(), parsed));
97                }
98            }
99            if let Some(Value::Array(fa)) = m.get("fields") {
100                fields = fa
101                    .iter()
102                    .filter_map(|f| f.as_str().map(String::from))
103                    .collect();
104            }
105        }
106        InjectInfo { relations, fields }
107    }
108}
109
110fn dep_slot<'a>(deps: &'a mut Vec<RelDep>, name: &str) -> &'a mut RelDep {
111    let idx = match deps.iter().position(|d| d.name == name) {
112        Some(i) => i,
113        None => {
114            deps.push(RelDep {
115                name: name.to_string(),
116                fields: Vec::new(),
117            });
118            deps.len() - 1
119        }
120    };
121    &mut deps[idx]
122}
123
124/// R7:收集 asyncFn 计算列的**普通字段**依赖(非关系子字段、非关系名、非 `_id`),
125/// 并入根 AST 字段(供投影取数与 `process_node` 裁剪保留),返回被注入的字段列表。
126///
127/// 关系子字段/关系名依赖由 [`collect_rel_deps`] 处理;这里只处理根级标量字段。
128pub fn collect_field_deps(fields: &mut Vec<String>, schema: &Schema) -> Vec<String> {
129    let cache = ensure_cache(schema);
130    let mut added = Vec::new();
131    for entry in &cache.async_fn_list {
132        for dep in &entry.depends {
133            let trimmed = dep.trim();
134            if trimmed.is_empty() || trimmed == "_id" {
135                continue;
136            }
137            // 关系子字段(`lessons{duration}`)或关系名 → 由 collect_rel_deps 负责,跳过
138            if trimmed.contains('{') || schema.relations.contains_key(trimmed) {
139                continue;
140            }
141            if !fields.contains(&trimmed.to_string()) {
142                fields.push(trimmed.to_string());
143                added.push(trimmed.to_string());
144            }
145        }
146    }
147    added
148}
149
150/// 收集所有 asyncFn 计算列 depends 中的关系字段需求(对应 JS `_collectRelDeps`)
151pub fn collect_rel_deps(schema: &Schema) -> Result<Vec<RelDep>, String> {
152    let cache = ensure_cache(schema);
153    let mut deps: Vec<RelDep> = Vec::new();
154
155    for entry in &cache.async_fn_list {
156        for dep in &entry.depends {
157            let trimmed = dep.trim();
158            if trimmed.is_empty() || trimmed == "_id" {
159                continue;
160            }
161            if trimmed.contains('{') {
162                let parsed = parse(&tokenize(trimmed))?;
163                if !schema.relations.contains_key(&parsed.model) {
164                    continue;
165                }
166                let slot = dep_slot(&mut deps, &parsed.model);
167                for f in &parsed.fields {
168                    if f != "_id" && !slot.fields.contains(f) {
169                        slot.fields.push(f.clone());
170                    }
171                }
172            } else {
173                if !schema.relations.contains_key(trimmed) {
174                    continue;
175                }
176                dep_slot(&mut deps, trimmed);
177            }
178        }
179    }
180
181    Ok(deps)
182}
183
184/// 把关系字段需求合并注入到 AST(对应 JS `_injectIntoAst`)
185pub fn inject_into_ast(relations: &mut Vec<(String, RelAst)>, rel_deps: &[RelDep]) -> InjectInfo {
186    let mut info = InjectInfo::default();
187
188    for dep in rel_deps {
189        match relations.iter().position(|(n, _)| n == &dep.name) {
190            Some(idx) => {
191                let (_, rel_ast) = &mut relations[idx];
192                let mut added = Vec::new();
193                for f in &dep.fields {
194                    if !rel_ast.fields.contains(f) {
195                        rel_ast.fields.push(f.clone());
196                        added.push(f.clone());
197                    }
198                }
199                if !added.is_empty() {
200                    info.relations
201                        .push((dep.name.clone(), Injected::Fields(added)));
202                }
203            }
204            None => {
205                relations.push((
206                    dep.name.clone(),
207                    RelAst {
208                        fields: dep.fields.clone(),
209                        relations: Vec::new(),
210                        params: HashMap::new(),
211                    },
212                ));
213                info.relations.push((dep.name.clone(), Injected::All));
214            }
215        }
216    }
217
218    info
219}
220
221/// 收集 asyncFn depends 的 GQL 片段并合并注入到查询 AST(对应 JS `_mergeDependsIntoAst`)
222pub fn merge_depends_into_ast(
223    relations: &mut Vec<(String, RelAst)>,
224    schema: &Schema,
225) -> Result<InjectInfo, String> {
226    if ensure_cache(schema).async_fn_list.is_empty() {
227        return Ok(InjectInfo::default());
228    }
229    let rel_deps = collect_rel_deps(schema)?;
230    if rel_deps.is_empty() {
231        return Ok(InjectInfo::default());
232    }
233    Ok(inject_into_ast(relations, &rel_deps))
234}
235
236/// 从结果中裁剪依赖注入的字段(对应 JS `_stripDepInjected`)
237pub fn strip_dep_injected(items: &mut [Value], info: &InjectInfo) {
238    if info.is_empty() {
239        return;
240    }
241    for item in items.iter_mut() {
242        let Some(o) = item.as_object_mut() else {
243            continue;
244        };
245        // R7:剥离注入的 asyncFn 普通字段依赖(如 `name`),避免泄漏到最终输出
246        if !info.fields.is_empty() {
247            for f in &info.fields {
248                o.remove(f);
249            }
250        }
251        for (rel_name, injected) in &info.relations {
252            match injected {
253                Injected::All => {
254                    o.remove(rel_name);
255                }
256                Injected::Fields(fields) => match o.get_mut(rel_name) {
257                    Some(Value::Array(arr)) => {
258                        for sub in arr.iter_mut() {
259                            if let Some(so) = sub.as_object_mut() {
260                                for f in fields {
261                                    so.remove(f);
262                                }
263                            }
264                        }
265                    }
266                    Some(Value::Object(so)) => {
267                        for f in fields {
268                            so.remove(f);
269                        }
270                    }
271                    _ => {}
272                },
273            }
274        }
275    }
276}