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::{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())?;
if let Some(cmd) = check_write_perm(
registry,
schema,
ctx,
condition,
ERR_NO_WRITE,
probe,
WriteAction::Update,
)? {
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),
);
Ok(json!({ "command": command }))
}
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 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);
}
return Ok(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,
}));
} 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);
Ok(json!({
"archiveCollection": archive_collection,
"findCommand": find_command,
"deleteCommand": delete_command,
}))
}
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 }))
}