rust-store-core 3.2.0

One data engine for MongoDB, MySQL, PostgreSQL and SQLite: a MongoDB-style GQL query language, permission checks, computed columns, command planning and SQL dialect translation. Pure logic, no IO.
Documentation
//! mutation 规划器(对应 JS `_mutationOne` + `_applyRelations`)
//!
//! 把一次 mutation 展开为**有序步骤序列**:每个步骤是一条命令(insertOne 或
//! findOneAndUpdate),父子步骤间的依赖(子文档外键 = 父文档 `_id`)用占位符表达:
//!
//! - `{{step.<N>._id}}`(见 [`step_id_placeholder`]):第 N 步执行结果文档的 `_id`,
//!   Host 在该步执行完成后回填到后续命令中
//! - 需要新生成 `_id` 的位置由 Host 供给的 `new_ids` 按序填充(与 JS `_generateId`
//!   的调用顺序一致;core 无随机源)
//!
//! 根步骤永远是 `steps[0]`;Host 依次执行后,用根步骤结果文档调
//! `apply_defaults_and_computes` 得到最终返回值。

use serde_json::{json, Map, Value};

use crate::permission::{can_write_schema, evaluate, Context, Doc};
use crate::rbac::{ensure_write, filter_writable_data_overlay, WriteAction};
use crate::schema::Registry;

use super::cmd::{cmd_find_one_and_update, cmd_insert_one};
use super::mutate::{
    build_upsert_conditions, build_upsert_update, needs_new_id, object_of, upsert_one_update,
    IdCursor,
};
use super::write::build_insert_doc;
use super::{ensure_context, step_id_placeholder, ERR_NO_WRITE};

/// 规划一条 mutation(对应 JS `mutation` 的单条分支 `_mutationOne`)。
///
/// 数组形态与空数组短路(`[]` / `null`)由 Host 处理:逐条调用本函数即可;
/// `new_ids` 由多条 mutation 共享一个游标语义(每次调用传剩余数组)。
pub fn plan_mutation(
    schema_name: &str,
    registry: &Registry,
    ctx: Option<&Context>,
    data: &Value,
    now: i64,
    new_ids: &[String],
) -> Result<Value, String> {
    ensure_context(registry, ctx)?;
    let mut out = PlanOut::default();
    let mut ids = IdCursor::new(new_ids);
    plan_mutation_node(schema_name, registry, ctx, data, now, &mut ids, &mut out)?;
    Ok(json!({ "steps": out.steps, "degraded": out.degraded }))
}

/// 规划期的可变累积器:有序步骤序列 + 降级声明(§11.4 静默点收口)。
#[derive(Default)]
struct PlanOut {
    steps: Vec<Value>,
    degraded: Vec<Value>,
}

/// 展开一个 mutation 节点:根写入步骤 + 各 relation 子步骤(`many` 递归)
fn plan_mutation_node(
    schema_name: &str,
    registry: &Registry,
    ctx: Option<&Context>,
    data: &Value,
    now: i64,
    ids: &mut IdCursor,
    out: &mut PlanOut,
) -> Result<(), String> {
    let schema = registry.get(schema_name)?;
    if !can_write_schema(registry.role_rules(), schema, ctx) {
        return Err(ERR_NO_WRITE.to_string());
    }
    // RBAC 表级写判定(mutation 步骤按 Insert 判,deny-wins;子模型递归同样受限)
    ensure_write(registry, schema, ctx, WriteAction::Insert)?;
    let Some(obj) = data.as_object() else {
        return Err("mutation 数据必须是对象".to_string());
    };

    // ── 1. 按 schema.relations 拆分 fieldData + relationData(保持遍历顺序) ──
    let mut field_data = Map::new();
    let mut relation_data: Vec<(&String, &Value)> = Vec::new();
    for (key, val) in obj {
        if let Some(rel_def) = schema.relations.get(key) {
            // JS:relDef.read && !evaluate(ctx, relDef.read) → 跳过该关系
            let allowed = match ctx {
                None => true,
                Some(c) => match &rel_def.read {
                    Some(rl) => evaluate(registry.role_rules(), Some(c), Some(rl), Doc::Missing),
                    None => true,
                },
            };
            if allowed {
                relation_data.push((key, val));
            } else {
                // §11.4 静默点收口(D2:绝不静默):关系 read 不通过时写入数据被跳过,
                // 但必须显式声明 —— 记入 `degraded:relationSkipped`,Host 经反馈通道告警。
                out.degraded.push(json!({
                    "code": "relationSkipped",
                    "layer": "mutation",
                    "message": format!(
                        "模型 {schema_name} 的关系 \"{key}\" 不可读(read 权限不通过),其写入数据已跳过"
                    ),
                    "hint": "授予该关系 read 权限;如需该情形显式报错,请在应用层先校验权限",
                }));
            }
        } else {
            field_data.insert(key.clone(), val.clone());
        }
    }

    let filtered_field = match ctx {
        // RBAC 感知版:静态 writable ∩ RBAC writeFields(无策略时直通静态过滤)
        Some(_) => filter_writable_data_overlay(registry, schema, ctx, &Value::Object(field_data)),
        None => Value::Object(field_data),
    };

    // ── 2/3. 写入 parent(upsert 或 insert 路径在规划期即可确定) ──
    let step_idx = out.steps.len();
    let parent_ph = step_id_placeholder(step_idx);
    let or_conditions = build_upsert_conditions(schema, &filtered_field);
    let command = if or_conditions.is_empty() {
        // Insert 路径(复用 JS `insert` 逻辑;仅需要新 `_id` 时才消耗游标)
        let new_id = if needs_new_id(schema, &filtered_field) {
            ids.next()?
        } else {
            ""
        };
        let doc = build_insert_doc(registry, schema, ctx, &filtered_field, now, new_id)?;
        cmd_insert_one(schema, &Value::Object(doc))
    } else {
        // Upsert 路径(JS `_buildUpsertUpdate` 仅在无 _id 时才 `_generateId`)
        let new_id = if needs_new_id(schema, &filtered_field) {
            ids.next()?
        } else {
            ""
        };
        let update_doc = build_upsert_update(schema, &filtered_field, new_id, now);
        let fu_options = json!({ "upsert": true, "returnDocument": "after" });
        cmd_find_one_and_update(
            schema,
            &json!({ "$or": or_conditions }),
            &update_doc,
            &fu_options,
        )
    };
    out.steps
        .push(json!({ "model": schema_name, "command": command }));

    // ── 4. 处理 relation 子文档 ──
    for (rel_name, rel_val) in relation_data {
        if rel_val.is_null() {
            continue;
        }
        // checked 索引:数据键未在 relations 定义时显式报错,绝不 panic
        let rel_def = schema
            .relations
            .get(rel_name)
            .ok_or_else(|| format!("模型 {} 的数据含未定义关系字段 {}", schema_name, rel_name))?;
        let rel_schema = registry.get(&rel_def.model)?;

        if rel_def.rel_type == "one" {
            // type:'one' 子文档强制按 foreignKey upsert(写入形态对齐 JS `_upsertOne`;
            // 权限门禁与字段过滤与 many 路径对齐:can_write_schema + filter_writable_data,
            // 拥有父模型写权限 ≠ 拥有子模型写权限)
            if !can_write_schema(registry.role_rules(), rel_schema, ctx) {
                return Err(ERR_NO_WRITE.to_string());
            }
            // RBAC 表级写判定(one 子步骤同样叠加,deny-wins)
            ensure_write(registry, rel_schema, ctx, WriteAction::Insert)?;
            let mut child = object_of(rel_val);
            child.insert(rel_def.foreign_field.clone(), json!(parent_ph));
            // 注入外键后再过滤(与 many 路径 plan_mutation_node 的过滤时机一致)
            let child_val = match ctx {
                // RBAC 感知版:静态 writable ∩ RBAC writeFields
                Some(_) => {
                    filter_writable_data_overlay(registry, rel_schema, ctx, &Value::Object(child))
                }
                None => Value::Object(child),
            };
            // JS `_upsertOne`:仅无 _id 时才 `_generateId`
            let new_id = if needs_new_id(rel_schema, &child_val) {
                ids.next()?
            } else {
                ""
            };
            let update_doc = upsert_one_update(rel_schema, &child_val, new_id, now);
            let filter = json!({ rel_def.foreign_field.clone(): parent_ph });
            let fu_options = json!({ "upsert": true, "returnDocument": "after" });
            let cmd = cmd_find_one_and_update(rel_schema, &filter, &update_doc, &fu_options);
            out.steps
                .push(json!({ "model": rel_def.model, "command": cmd }));
        } else if rel_def.rel_type == "many" {
            let arr: Vec<Value> = match rel_val {
                Value::Array(a) => a.clone(),
                _ => vec![rel_val.clone()],
            };
            for child in arr {
                if child.is_null() {
                    continue;
                }
                let mut c = object_of(&child);
                c.insert(rel_def.foreign_field.clone(), json!(parent_ph));
                plan_mutation_node(
                    &rel_def.model,
                    registry,
                    ctx,
                    &Value::Object(c),
                    now,
                    ids,
                    out,
                )?;
            }
        } else {
            // §11.4 静默点收口(D2:绝不静默):未知 rel_type 无法规划写入步骤,
            // 显式报错(原行为为静默跳过该关系)。
            return Err(format!(
                "关系 \"{rel_name}\" 的 rel_type \"{}\" 不被支持(仅支持 one / many;D2:绝不静默跳过)",
                rel_def.rel_type
            ));
        }
    }

    Ok(())
}