1use serde_json::{json, Map, Value};
4
5use crate::computes::{apply_defaults_and_computes, FnRegistry};
6use crate::permission::{can_write_schema, evaluate, merge_owner_condition, Context, Doc};
7use crate::rbac::{
8 ensure_write, ensure_write_on_doc, filter_writable_data_overlay, merge_row_condition,
9 probe_condition_fields, WriteAction,
10};
11use crate::schema::{Registry, Schema};
12use crate::types::{
13 has_relation_predicate, is_truthy, validate_condition, validate_condition_shape,
14};
15
16use super::cmd::{cmd_count_documents, cmd_find_one, cmd_insert_one};
17use super::write_links::attach_write_links;
18use super::{ensure_context, ERR_NO_WRITE};
19
20pub fn plan_insert(
24 schema_name: &str,
25 registry: &Registry,
26 ctx: Option<&Context>,
27 data: &Value,
28 now: i64,
29 new_id: &str,
30 fn_registry: Option<&dyn FnRegistry>,
31) -> Result<Value, String> {
32 ensure_context(registry, ctx)?;
33 let schema = registry.get(schema_name)?;
34
35 if !can_write_schema(registry.role_rules(), schema, ctx) {
36 return Err(ERR_NO_WRITE.to_string());
37 }
38 ensure_write(registry, schema, ctx, WriteAction::Insert)?;
40
41 let doc = build_insert_doc(registry, schema, ctx, data, now, new_id)?;
42 let doc = Value::Object(doc);
43 let triggers = super::triggers::expand_triggers(registry, ctx, schema_name, "insert")?;
45 let mut plan = json!({
46 "command": cmd_insert_one(schema, &doc),
47 "returns": apply_defaults_and_computes(&doc, schema, fn_registry)?,
48 });
49 if !triggers.is_empty() {
51 plan.as_object_mut()
52 .expect("plan_insert:plan 必为对象")
53 .insert("triggers".to_string(), json!(triggers));
54 }
55 attach_write_links(plan, registry, &[schema_name], registry.write_link_policy())
57}
58
59pub(super) fn build_insert_doc(
62 registry: &Registry,
63 schema: &Schema,
64 ctx: Option<&Context>,
65 data: &Value,
66 now: i64,
67 new_id: &str,
68) -> Result<Map<String, Value>, String> {
69 let filtered = match ctx {
70 Some(_) => filter_writable_data_overlay(registry, schema, ctx, data),
72 None => data.clone(),
73 };
74
75 let mut doc: Map<String, Value> = filtered.as_object().cloned().unwrap_or_default();
78
79 if !doc.get("_id").map(is_truthy).unwrap_or(false) {
83 if schema.id_is_autoincrement() {
84 } else if !schema.id_prefix.is_empty() {
89 doc.insert("_id".to_string(), json!(new_id));
90 } else {
91 return Err(format!(
92 "schema \"{}\" 未配置 idPrefix 且未提供有效 _id:请显式提供 _id,或为 schema 配置 idPrefix",
93 schema.name
94 ));
95 }
96 }
97
98 if has_creator_permission(schema) && !doc.get("createdBy").map(is_truthy).unwrap_or(false) {
100 if let Some(uid) = ctx.and_then(|c| c.user_id.clone()) {
101 doc.insert("createdBy".to_string(), json!(uid));
102 }
103 }
104
105 if schema.timestamps {
107 if !doc.get("createdAt").map(is_truthy).unwrap_or(false) {
108 doc.insert("createdAt".to_string(), json!(now));
109 }
110 doc.insert("updatedAt".to_string(), json!(now));
111 }
112 Ok(doc)
113}
114
115pub fn plan_exists(
117 schema_name: &str,
118 registry: &Registry,
119 condition: &Value,
120) -> Result<Value, String> {
121 validate_condition(condition)?;
123 let schema = registry.get(schema_name)?;
124 validate_condition_shape(schema, condition, registry.profile())?;
126 if has_relation_predicate(schema, condition) {
128 return Err(
129 "exists 不支持关系聚合谓词条件(§9.6):标量查询无法表达关系谓词,\
130 请改用 query 并自行判断是否有结果"
131 .to_string(),
132 );
133 }
134 Ok(cmd_find_one(schema, condition, Some(&json!({ "_id": 1 }))))
135}
136
137pub fn plan_count(
142 schema_name: &str,
143 registry: &Registry,
144 filter: Option<&Value>,
145 ctx: Option<&Context>,
146) -> Result<Value, String> {
147 if let Some(f) = filter {
148 validate_condition(f)?;
150 }
151 let schema = registry.get(schema_name)?;
152 if let Some(f) = filter {
154 validate_condition_shape(schema, f, registry.profile())?;
155 if has_relation_predicate(schema, f) {
157 return Err(
158 "count 不支持关系聚合谓词条件(§9.6):标量计数无法表达关系谓词,\
159 请改用 query 并在调用方自行统计"
160 .to_string(),
161 );
162 }
163 }
164 let base = match filter {
165 None | Some(Value::Null) => json!({}),
166 Some(v) => v.clone(),
167 };
168 let owner_merged = merge_owner_condition(registry.role_rules(), schema, ctx, Some(base));
170 let filter = merge_row_condition(registry, schema, ctx, "read", owner_merged)
171 .unwrap_or_else(|| json!({}));
172 Ok(cmd_count_documents(schema, &filter))
173}
174
175pub(super) fn has_creator_permission(schema: &Schema) -> bool {
176 let has = |list: &Option<Vec<String>>| {
177 list.as_ref()
178 .map(|l| l.iter().any(|r| r == "creator"))
179 .unwrap_or(false)
180 };
181 has(&schema.read) || has(&schema.write)
182}
183
184#[derive(Debug, Clone, Copy)]
191pub enum Probe<'a> {
192 NotProbed,
193 NoResult,
194 Found(&'a Value),
195}
196
197#[allow(clippy::too_many_arguments)]
215pub fn check_write_perm(
216 registry: &Registry,
217 schema: &Schema,
218 ctx: Option<&Context>,
219 condition: &Value,
220 deny_msg: &str,
221 probe: Probe,
222 action: WriteAction,
223 extra_probe_fields: &[String],
224) -> Result<Option<Value>, String> {
225 let probe_projection = |extra: &[String]| -> Value {
226 let mut m = Map::new();
227 m.insert("_id".to_string(), json!(1));
228 m.insert("createdBy".to_string(), json!(1));
229 for f in extra {
230 if schema.fields.contains_key(f) {
232 m.insert(f.clone(), json!(1));
233 }
234 }
235 Value::Object(m)
236 };
237
238 if ctx.is_none() {
244 if !extra_probe_fields.is_empty() {
245 if let Probe::NotProbed = probe {
246 let mut m = Map::new();
247 m.insert("_id".to_string(), json!(1));
248 for f in extra_probe_fields {
249 if schema.fields.contains_key(f) {
251 m.insert(f.clone(), json!(1));
252 }
253 }
254 return Ok(Some(cmd_find_one(
255 schema,
256 condition,
257 Some(&Value::Object(m)),
258 )));
259 }
260 }
261 return Ok(None);
262 }
263 let Some(c) = ctx else {
264 unreachable!("上方已处理 ctx.is_none()");
265 };
266 let rules = registry.role_rules();
268 if crate::permission::has_any_role(c, &rules.deny_write_roles) {
269 return Err(deny_msg.to_string());
270 }
271 ensure_write(registry, schema, Some(c), action)?;
273 let rbac_row_restricted =
275 crate::rbac::row_condition(registry, schema, Some(c), action.as_str()).is_some();
276
277 let mut merged = probe_condition_fields(registry, schema, Some(c), action.as_str());
279 for f in extra_probe_fields {
280 if !merged.contains(f) {
281 merged.push(f.clone());
282 }
283 }
284
285 if can_write_schema(registry.role_rules(), schema, Some(c)) {
286 if rbac_row_restricted && is_truthy(condition) {
288 return match probe {
289 Probe::NotProbed => Ok(Some(cmd_find_one(
290 schema,
291 condition,
292 Some(&probe_projection(&merged)),
293 ))),
294 Probe::NoResult => Err(deny_msg.to_string()),
295 Probe::Found(doc) => {
296 ensure_write_on_doc(registry, schema, Some(c), action, doc).map(|_| None)
297 }
298 };
299 }
300 if !extra_probe_fields.is_empty() {
303 return match probe {
304 Probe::NotProbed => Ok(Some(cmd_find_one(
305 schema,
306 condition,
307 Some(&probe_projection(&merged)),
308 ))),
309 Probe::NoResult => Err(deny_msg.to_string()),
310 Probe::Found(_) => Ok(None),
311 };
312 }
313 return Ok(None);
314 }
315 let creator_only = schema
316 .write
317 .as_ref()
318 .map(|w| w.iter().any(|r| r == "creator"))
319 .unwrap_or(false);
320 if creator_only && is_truthy(condition) {
321 let needs_row_check = rbac_row_restricted;
322 return match probe {
323 Probe::NotProbed => Ok(Some(cmd_find_one(
324 schema,
325 condition,
326 Some(&probe_projection(&merged)),
327 ))),
328 Probe::NoResult => Err(deny_msg.to_string()),
329 Probe::Found(doc) => {
330 if evaluate(
331 registry.role_rules(),
332 Some(c),
333 schema.write.as_deref(),
334 Doc::Doc(doc),
335 ) {
336 if needs_row_check {
337 ensure_write_on_doc(registry, schema, Some(c), action, doc).map(|_| None)
339 } else {
340 Ok(None)
341 }
342 } else {
343 Err(deny_msg.to_string())
344 }
345 }
346 };
347 }
348 Err(deny_msg.to_string())
349}