Skip to main content

rust_store_core/command/
mutation.rs

1//! mutation 规划器(对应 JS `_mutationOne` + `_applyRelations`)
2//!
3//! 把一次 mutation 展开为**有序步骤序列**:每个步骤是一条命令(insertOne 或
4//! findOneAndUpdate),父子步骤间的依赖(子文档外键 = 父文档 `_id`)用占位符表达:
5//!
6//! - `{{step.<N>._id}}`(见 [`step_id_placeholder`]):第 N 步执行结果文档的 `_id`,
7//!   Host 在该步执行完成后回填到后续命令中
8//! - 需要新生成 `_id` 的位置由 Host 供给的 `new_ids` 按序填充(与 JS `_generateId`
9//!   的调用顺序一致;core 无随机源)
10//!
11//! 根步骤永远是 `steps[0]`;Host 依次执行后,用根步骤结果文档调
12//! `apply_defaults_and_computes` 得到最终返回值。
13
14use serde_json::{json, Map, Value};
15
16use crate::permission::{can_write_schema, evaluate, Context, Doc};
17use crate::rbac::{ensure_write, filter_writable_data_overlay, WriteAction};
18use crate::schema::Registry;
19
20use super::cmd::{cmd_find_one_and_update, cmd_insert_one};
21use super::mutate::{
22    build_upsert_conditions, build_upsert_update, needs_new_id, object_of, upsert_one_update,
23    IdCursor,
24};
25use super::write::build_insert_doc;
26use super::{ensure_context, step_id_placeholder, ERR_NO_WRITE};
27
28/// 规划一条 mutation(对应 JS `mutation` 的单条分支 `_mutationOne`)。
29///
30/// 数组形态与空数组短路(`[]` / `null`)由 Host 处理:逐条调用本函数即可;
31/// `new_ids` 由多条 mutation 共享一个游标语义(每次调用传剩余数组)。
32pub fn plan_mutation(
33    schema_name: &str,
34    registry: &Registry,
35    ctx: Option<&Context>,
36    data: &Value,
37    now: i64,
38    new_ids: &[String],
39) -> Result<Value, String> {
40    ensure_context(registry, ctx)?;
41    let mut out = PlanOut::default();
42    let mut ids = IdCursor::new(new_ids);
43    plan_mutation_node(schema_name, registry, ctx, data, now, &mut ids, &mut out)?;
44    Ok(json!({ "steps": out.steps, "degraded": out.degraded }))
45}
46
47/// 规划期的可变累积器:有序步骤序列 + 降级声明(§11.4 静默点收口)。
48#[derive(Default)]
49struct PlanOut {
50    steps: Vec<Value>,
51    degraded: Vec<Value>,
52}
53
54/// 展开一个 mutation 节点:根写入步骤 + 各 relation 子步骤(`many` 递归)
55fn plan_mutation_node(
56    schema_name: &str,
57    registry: &Registry,
58    ctx: Option<&Context>,
59    data: &Value,
60    now: i64,
61    ids: &mut IdCursor,
62    out: &mut PlanOut,
63) -> Result<(), String> {
64    let schema = registry.get(schema_name)?;
65    if !can_write_schema(schema, ctx) {
66        return Err(ERR_NO_WRITE.to_string());
67    }
68    // RBAC 表级写判定(mutation 步骤按 Insert 判,deny-wins;子模型递归同样受限)
69    ensure_write(registry, schema, ctx, WriteAction::Insert)?;
70    let Some(obj) = data.as_object() else {
71        return Err("mutation 数据必须是对象".to_string());
72    };
73
74    // ── 1. 按 schema.relations 拆分 fieldData + relationData(保持遍历顺序) ──
75    let mut field_data = Map::new();
76    let mut relation_data: Vec<(&String, &Value)> = Vec::new();
77    for (key, val) in obj {
78        if let Some(rel_def) = schema.relations.get(key) {
79            // JS:relDef.read && !evaluate(ctx, relDef.read) → 跳过该关系
80            let allowed = match ctx {
81                None => true,
82                Some(c) => match &rel_def.read {
83                    Some(rl) => evaluate(Some(c), Some(rl), Doc::Missing),
84                    None => true,
85                },
86            };
87            if allowed {
88                relation_data.push((key, val));
89            } else {
90                // §11.4 静默点收口(D2:绝不静默):关系 read 不通过时写入数据被跳过,
91                // 但必须显式声明 —— 记入 `degraded:relationSkipped`,Host 经反馈通道告警。
92                out.degraded.push(json!({
93                    "code": "relationSkipped",
94                    "layer": "mutation",
95                    "message": format!(
96                        "模型 {schema_name} 的关系 \"{key}\" 不可读(read 权限不通过),其写入数据已跳过"
97                    ),
98                    "hint": "授予该关系 read 权限;如需该情形显式报错,请在应用层先校验权限",
99                }));
100            }
101        } else {
102            field_data.insert(key.clone(), val.clone());
103        }
104    }
105
106    let filtered_field = match ctx {
107        // RBAC 感知版:静态 writable ∩ RBAC writeFields(无策略时直通静态过滤)
108        Some(_) => filter_writable_data_overlay(registry, schema, ctx, &Value::Object(field_data)),
109        None => Value::Object(field_data),
110    };
111
112    // ── 2/3. 写入 parent(upsert 或 insert 路径在规划期即可确定) ──
113    let step_idx = out.steps.len();
114    let parent_ph = step_id_placeholder(step_idx);
115    let or_conditions = build_upsert_conditions(schema, &filtered_field);
116    let command = if or_conditions.is_empty() {
117        // Insert 路径(复用 JS `insert` 逻辑;仅需要新 `_id` 时才消耗游标)
118        let new_id = if needs_new_id(schema, &filtered_field) {
119            ids.next()?
120        } else {
121            ""
122        };
123        let doc = build_insert_doc(registry, schema, ctx, &filtered_field, now, new_id)?;
124        cmd_insert_one(schema, &Value::Object(doc))
125    } else {
126        // Upsert 路径(JS `_buildUpsertUpdate` 仅在无 _id 时才 `_generateId`)
127        let new_id = if needs_new_id(schema, &filtered_field) {
128            ids.next()?
129        } else {
130            ""
131        };
132        let update_doc = build_upsert_update(schema, &filtered_field, new_id, now);
133        let fu_options = json!({ "upsert": true, "returnDocument": "after" });
134        cmd_find_one_and_update(
135            schema,
136            &json!({ "$or": or_conditions }),
137            &update_doc,
138            &fu_options,
139        )
140    };
141    out.steps
142        .push(json!({ "model": schema_name, "command": command }));
143
144    // ── 4. 处理 relation 子文档 ──
145    for (rel_name, rel_val) in relation_data {
146        if rel_val.is_null() {
147            continue;
148        }
149        // checked 索引:数据键未在 relations 定义时显式报错,绝不 panic
150        let rel_def = schema
151            .relations
152            .get(rel_name)
153            .ok_or_else(|| format!("模型 {} 的数据含未定义关系字段 {}", schema_name, rel_name))?;
154        let rel_schema = registry.get(&rel_def.model)?;
155
156        if rel_def.rel_type == "one" {
157            // type:'one' 子文档强制按 foreignKey upsert(写入形态对齐 JS `_upsertOne`;
158            // 权限门禁与字段过滤与 many 路径对齐:can_write_schema + filter_writable_data,
159            // 拥有父模型写权限 ≠ 拥有子模型写权限)
160            if !can_write_schema(rel_schema, ctx) {
161                return Err(ERR_NO_WRITE.to_string());
162            }
163            // RBAC 表级写判定(one 子步骤同样叠加,deny-wins)
164            ensure_write(registry, rel_schema, ctx, WriteAction::Insert)?;
165            let mut child = object_of(rel_val);
166            child.insert(rel_def.foreign_field.clone(), json!(parent_ph));
167            // 注入外键后再过滤(与 many 路径 plan_mutation_node 的过滤时机一致)
168            let child_val = match ctx {
169                // RBAC 感知版:静态 writable ∩ RBAC writeFields
170                Some(_) => {
171                    filter_writable_data_overlay(registry, rel_schema, ctx, &Value::Object(child))
172                }
173                None => Value::Object(child),
174            };
175            // JS `_upsertOne`:仅无 _id 时才 `_generateId`
176            let new_id = if needs_new_id(rel_schema, &child_val) {
177                ids.next()?
178            } else {
179                ""
180            };
181            let update_doc = upsert_one_update(rel_schema, &child_val, new_id, now);
182            let filter = json!({ rel_def.foreign_field.clone(): parent_ph });
183            let fu_options = json!({ "upsert": true, "returnDocument": "after" });
184            let cmd = cmd_find_one_and_update(rel_schema, &filter, &update_doc, &fu_options);
185            out.steps
186                .push(json!({ "model": rel_def.model, "command": cmd }));
187        } else if rel_def.rel_type == "many" {
188            let arr: Vec<Value> = match rel_val {
189                Value::Array(a) => a.clone(),
190                _ => vec![rel_val.clone()],
191            };
192            for child in arr {
193                if child.is_null() {
194                    continue;
195                }
196                let mut c = object_of(&child);
197                c.insert(rel_def.foreign_field.clone(), json!(parent_ph));
198                plan_mutation_node(
199                    &rel_def.model,
200                    registry,
201                    ctx,
202                    &Value::Object(c),
203                    now,
204                    ids,
205                    out,
206                )?;
207            }
208        } else {
209            // §11.4 静默点收口(D2:绝不静默):未知 rel_type 无法规划写入步骤,
210            // 显式报错(原行为为静默跳过该关系)。
211            return Err(format!(
212                "关系 \"{rel_name}\" 的 rel_type \"{}\" 不被支持(仅支持 one / many;D2:绝不静默跳过)",
213                rel_def.rel_type
214            ));
215        }
216    }
217
218    Ok(())
219}