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 if let Some(obj) = filtered.as_object() {
76 crate::access::validate_write_values(schema, ctx, obj)?;
77 }
78
79 let mut doc: Map<String, Value> = filtered.as_object().cloned().unwrap_or_default();
82
83 if !doc.get("_id").map(is_truthy).unwrap_or(false) {
87 if schema.id_is_autoincrement() {
88 } else if !schema.id_prefix.is_empty() {
93 doc.insert("_id".to_string(), json!(new_id));
94 } else {
95 return Err(format!(
96 "schema \"{}\" 未配置 idPrefix 且未提供有效 _id:请显式提供 _id,或为 schema 配置 idPrefix",
97 schema.name
98 ));
99 }
100 }
101
102 if has_creator_permission(schema) && !doc.get("createdBy").map(is_truthy).unwrap_or(false) {
104 if let Some(uid) = ctx.and_then(|c| c.user_id.clone()) {
105 doc.insert("createdBy".to_string(), json!(uid));
106 }
107 }
108
109 if schema.timestamps {
111 if !doc.get("createdAt").map(is_truthy).unwrap_or(false) {
112 doc.insert("createdAt".to_string(), json!(now));
113 }
114 doc.insert("updatedAt".to_string(), json!(now));
115 }
116 Ok(doc)
117}
118
119pub fn plan_exists(
121 schema_name: &str,
122 registry: &Registry,
123 condition: &Value,
124) -> Result<Value, String> {
125 validate_condition(condition)?;
127 let schema = registry.get(schema_name)?;
128 validate_condition_shape(schema, condition, registry.profile())?;
130 if has_relation_predicate(schema, condition) {
132 return Err(
133 "exists 不支持关系聚合谓词条件(§9.6):标量查询无法表达关系谓词,\
134 请改用 query 并自行判断是否有结果"
135 .to_string(),
136 );
137 }
138 Ok(cmd_find_one(schema, condition, Some(&json!({ "_id": 1 }))))
139}
140
141pub fn plan_count(
146 schema_name: &str,
147 registry: &Registry,
148 filter: Option<&Value>,
149 ctx: Option<&Context>,
150) -> Result<Value, String> {
151 if let Some(f) = filter {
152 validate_condition(f)?;
154 }
155 let schema = registry.get(schema_name)?;
156 if let Some(f) = filter {
158 validate_condition_shape(schema, f, registry.profile())?;
159 if has_relation_predicate(schema, f) {
161 return Err(
162 "count 不支持关系聚合谓词条件(§9.6):标量计数无法表达关系谓词,\
163 请改用 query 并在调用方自行统计"
164 .to_string(),
165 );
166 }
167 }
168 let base = match filter {
169 None | Some(Value::Null) => json!({}),
170 Some(v) => v.clone(),
171 };
172 let owner_merged = merge_owner_condition(registry.role_rules(), schema, ctx, Some(base));
174 let filter = merge_row_condition(registry, schema, ctx, "read", owner_merged)?
175 .unwrap_or_else(|| json!({}));
176 Ok(cmd_count_documents(schema, &filter))
177}
178
179pub(super) fn has_creator_permission(schema: &Schema) -> bool {
180 let has = |list: &Option<Vec<String>>| {
181 list.as_ref()
182 .map(|l| l.iter().any(|r| r == "creator"))
183 .unwrap_or(false)
184 };
185 has(&schema.read) || has(&schema.write)
186}
187
188#[derive(Debug, Clone, Copy)]
195pub enum Probe<'a> {
196 NotProbed,
197 NoResult,
198 Found(&'a Value),
199}
200
201#[allow(clippy::too_many_arguments)]
219pub fn check_write_perm(
220 registry: &Registry,
221 schema: &Schema,
222 ctx: Option<&Context>,
223 condition: &Value,
224 deny_msg: &str,
225 probe: Probe,
226 action: WriteAction,
227 extra_probe_fields: &[String],
228) -> Result<Option<Value>, String> {
229 let probe_projection = |extra: &[String]| -> Value {
230 let mut m = Map::new();
231 m.insert("_id".to_string(), json!(1));
232 m.insert(schema.owner_key().to_string(), json!(1));
235 for f in extra {
236 if schema.fields.contains_key(f) {
238 m.insert(f.clone(), json!(1));
239 }
240 }
241 Value::Object(m)
242 };
243
244 if ctx.is_none() {
250 if !extra_probe_fields.is_empty() {
251 if let Probe::NotProbed = probe {
252 let mut m = Map::new();
253 m.insert("_id".to_string(), json!(1));
254 for f in extra_probe_fields {
255 if schema.fields.contains_key(f) {
257 m.insert(f.clone(), json!(1));
258 }
259 }
260 return Ok(Some(cmd_find_one(
261 schema,
262 condition,
263 Some(&Value::Object(m)),
264 )));
265 }
266 }
267 return Ok(None);
268 }
269 let Some(c) = ctx else {
270 unreachable!("上方已处理 ctx.is_none()");
271 };
272 let rules = registry.role_rules();
274 if crate::permission::has_any_role(c, &rules.deny_write_roles) {
275 return Err(deny_msg.to_string());
276 }
277 ensure_write(registry, schema, Some(c), action)?;
279 let access_restricted = crate::access::write_restricted(schema, Some(c))?;
281 let has_condition = condition
284 .as_object()
285 .map(|o| !o.is_empty())
286 .unwrap_or_else(|| is_truthy(condition));
287 if access_restricted && !has_condition {
288 return Err(format!(
289 "{}条件为空,拒绝无条件下写入受 accessKeys 管控的模型",
290 crate::access::ERR_ACCESS_PREFIX
291 ));
292 }
293 let rbac_row_restricted =
295 crate::rbac::row_condition(registry, schema, Some(c), action.as_str()).is_some();
296 let row_restricted = rbac_row_restricted || access_restricted;
297
298 let mut merged = probe_condition_fields(registry, schema, Some(c), action.as_str());
301 for f in crate::access::probe_fields(schema, Some(c))? {
302 if !merged.contains(&f) {
303 merged.push(f);
304 }
305 }
306 for f in extra_probe_fields {
307 if !merged.contains(f) {
308 merged.push(f.clone());
309 }
310 }
311
312 if can_write_schema(registry.role_rules(), schema, Some(c)) {
313 if row_restricted && is_truthy(condition) {
315 return match probe {
316 Probe::NotProbed => Ok(Some(cmd_find_one(
317 schema,
318 condition,
319 Some(&probe_projection(&merged)),
320 ))),
321 Probe::NoResult => Err(deny_msg.to_string()),
322 Probe::Found(doc) => {
323 if access_restricted {
324 crate::access::validate_doc_values(schema, Some(c), doc)?;
325 }
326 if rbac_row_restricted {
327 ensure_write_on_doc(registry, schema, Some(c), action, doc).map(|_| None)
328 } else {
329 Ok(None)
330 }
331 }
332 };
333 }
334 if !extra_probe_fields.is_empty() {
337 return match probe {
338 Probe::NotProbed => Ok(Some(cmd_find_one(
339 schema,
340 condition,
341 Some(&probe_projection(&merged)),
342 ))),
343 Probe::NoResult => Err(deny_msg.to_string()),
344 Probe::Found(_) => Ok(None),
345 };
346 }
347 return Ok(None);
348 }
349 let creator_only = schema
350 .write
351 .as_ref()
352 .map(|w| w.iter().any(|r| r == "creator"))
353 .unwrap_or(false);
354 if creator_only && is_truthy(condition) {
355 return match probe {
356 Probe::NotProbed => Ok(Some(cmd_find_one(
357 schema,
358 condition,
359 Some(&probe_projection(&merged)),
360 ))),
361 Probe::NoResult => Err(deny_msg.to_string()),
362 Probe::Found(doc) => {
363 if evaluate(
364 registry.role_rules(),
365 Some(c),
366 schema.write.as_deref(),
367 Doc::Doc(doc),
368 schema.owner_key(),
369 ) {
370 if access_restricted {
372 crate::access::validate_doc_values(schema, Some(c), doc)?;
373 }
374 if rbac_row_restricted {
375 ensure_write_on_doc(registry, schema, Some(c), action, doc).map(|_| None)
377 } else {
378 Ok(None)
379 }
380 } else {
381 Err(deny_msg.to_string())
382 }
383 }
384 };
385 }
386 Err(deny_msg.to_string())
387}