use serde_json::{json, Value};
use crate::command::cmd::{cmd_delete_many, cmd_find, cmd_find_one_and_update, cmd_insert_many};
use crate::command::write::{check_write_perm, Probe};
use crate::command::write_links::attach_write_links;
use crate::command::{ensure_context, ERR_NO_BATCH_WRITE, ERR_NO_DELETE, ERR_NO_WRITE};
use crate::permission::Context;
use crate::rbac::WriteAction;
use crate::schema::Registry;
use crate::types::{validate_condition, validate_condition_shape};
use super::{
build_raw_update, build_set_data, find_one_and_update_options, has_raw_operators,
is_blank_condition, object_of,
};
#[allow(clippy::too_many_arguments)]
pub fn plan_update(
schema_name: &str,
registry: &Registry,
ctx: Option<&Context>,
condition: &Value,
data: &Value,
options: &Value,
now: i64,
probe: Probe,
) -> Result<Value, String> {
ensure_context(registry, ctx)?;
let schema = registry.get(schema_name)?;
validate_condition(condition)?;
validate_condition_shape(schema, condition, registry.profile())?;
let extra = crate::command::triggers::before_probe_fields(
schema
.triggers
.get("update")
.map(|v| v.as_slice())
.unwrap_or(&[]),
);
if let Some(cmd) = check_write_perm(
registry,
schema,
ctx,
condition,
ERR_NO_WRITE,
probe,
WriteAction::Update,
&extra,
)? {
return Ok(json!({ "needsProbe": cmd }));
}
let update_doc = if has_raw_operators(data) {
build_raw_update(registry, schema, ctx, data, now)
} else {
let mut set_data = build_set_data(registry, schema, ctx, data);
if set_data.is_empty() {
return Err("没有提供要更新的字段".to_string());
}
if schema.timestamps {
set_data.insert("updatedAt".to_string(), json!(now));
}
json!({ "$set": Value::Object(set_data) })
};
let command = cmd_find_one_and_update(
schema,
condition,
&update_doc,
&find_one_and_update_options(options),
);
let triggers = crate::command::triggers::expand_triggers(registry, ctx, schema_name, "update")?;
let mut plan = json!({ "command": command });
if !triggers.is_empty() {
plan.as_object_mut()
.expect("plan_update:plan 必为对象")
.insert("triggers".to_string(), json!(triggers));
}
attach_write_links(plan, registry, &[schema_name], registry.write_link_policy())
}
pub fn plan_remove(
schema_name: &str,
registry: &Registry,
ctx: Option<&Context>,
condition: &Value,
probe: Probe,
) -> Result<Value, String> {
ensure_context(registry, ctx)?;
let schema = registry.get(schema_name)?;
validate_condition(condition)?;
validate_condition_shape(schema, condition, registry.profile())?;
if is_blank_condition(condition) {
return Err(ERR_NO_BATCH_WRITE.to_string());
}
if let Some(cmd) = check_write_perm(
registry,
schema,
ctx,
condition,
ERR_NO_DELETE,
probe,
WriteAction::Remove,
&[],
)? {
return Ok(json!({ "needsProbe": cmd }));
}
let remove_triggers = crate::command::triggers::expand_triggers(registry, ctx, schema_name, "remove")?;
let has_rel_pred = condition
.as_object()
.map(|m| m.keys().any(|k| schema.relations.contains_key(k)))
.unwrap_or(false);
let eff_condition = if has_rel_pred {
let pre = super::plan_rel_pred_mutation(schema, registry, ctx, condition)?;
let mut delete_command = cmd_delete_many(schema, &pre.condition);
if let Some(obj) = delete_command.as_object_mut() {
obj.insert("preCommand".to_string(), pre.lookup_command);
}
let mut plan = json!({
"archiveCollection": if registry.has(&archive_name_of(schema_name)) { json!(registry.get(&archive_name_of(schema_name))?.collection) } else { Value::Null },
"findCommand": cmd_find(schema, &pre.condition, None),
"deleteCommand": delete_command,
});
if !remove_triggers.is_empty() {
plan.as_object_mut()
.expect("plan_remove:plan 必为对象")
.insert("triggers".to_string(), json!(remove_triggers));
}
return attach_write_links(
plan,
registry,
&[schema_name],
registry.write_link_policy(),
);
} else {
condition
};
let archive_name = format!("{}Deleted", schema_name);
let (archive_collection, find_command) = if registry.has(&archive_name) {
let arch = registry.get(&archive_name)?;
(
json!(arch.collection),
Some(cmd_find(schema, eff_condition, None)),
)
} else {
(Value::Null, None)
};
let delete_command = cmd_delete_many(schema, eff_condition);
let mut plan = json!({
"archiveCollection": archive_collection,
"findCommand": find_command,
"deleteCommand": delete_command,
});
if !remove_triggers.is_empty() {
plan.as_object_mut()
.expect("plan_remove:plan 必为对象")
.insert("triggers".to_string(), json!(remove_triggers));
}
attach_write_links(
plan,
registry,
&[schema_name],
registry.write_link_policy(),
)
}
fn archive_name_of(schema_name: &str) -> String {
format!("{}Deleted", schema_name)
}
pub fn plan_archive_docs(
schema_name: &str,
registry: &Registry,
docs: &[Value],
now: i64,
) -> Result<Value, String> {
let archive_name = format!("{}Deleted", schema_name);
let arch = registry.get(&archive_name)?;
let archived: Vec<Value> = docs
.iter()
.map(|d| {
let mut o = object_of(d);
o.insert("deletedAt".to_string(), json!(now));
Value::Object(o)
})
.collect();
let mut command = cmd_insert_many(arch, &archived);
if let Some(obj) = command.as_object_mut() {
obj.insert("upsertById".to_string(), json!(true));
}
Ok(json!({ "command": command }))
}