rust_store_core/command/
count.rs1use serde_json::{json, Map, Value};
4
5use crate::permission::{merge_owner_condition, Context};
6use crate::pipeline::{param, parse_gql};
7use crate::rbac::merge_row_condition;
8use crate::schema::Registry;
9use crate::types::{has_relation_predicate, is_truthy};
10
11use super::cmd::{cmd_count_documents, num_value};
12use super::query::{plan_query_ast_mut, resolve_page, Page, QueryPlan};
13
14#[derive(Debug, Clone)]
15pub struct CountQueryPlan {
16 pub query: QueryPlan,
17 pub page: Page,
18 pub count_command: Value,
20}
21
22impl CountQueryPlan {
23 pub fn to_value(&self) -> Value {
24 let mut m = self
25 .query
26 .to_value()
27 .as_object()
28 .cloned()
29 .unwrap_or_default();
30 m.insert("countCommand".to_string(), self.count_command.clone());
31 if let Some(o) = self.page.to_value().as_object() {
32 for (k, v) in o {
33 m.insert(k.clone(), v.clone());
34 }
35 }
36 Value::Object(m)
37 }
38
39 pub fn has_more(&self, total: f64) -> bool {
41 self.page.has_more(total)
42 }
43}
44
45pub fn plan_query_with_count(
47 gql: &str,
48 params: &Map<String, Value>,
49 registry: &Registry,
50 ctx: Option<&Context>,
51) -> Result<CountQueryPlan, String> {
52 let mut ast = parse_gql(gql)?;
53 let schema = registry.get(&ast.model)?;
54
55 let mut params = params.clone();
56 let page = resolve_page(&ast, ¶ms);
57
58 let wants_pagination = params.contains_key("page") || params.contains_key("pageSize");
62 if wants_pagination && !ast.params.contains_key("skip") {
63 ast.params
64 .insert("skip".to_string(), "@__core_page_skip__".to_string());
65 params.insert(
66 "__core_page_skip__".to_string(),
67 num_value(page.page * page.page_size),
68 );
69 }
70 if wants_pagination && !ast.params.contains_key("limit") {
71 ast.params
72 .insert("limit".to_string(), "@__core_page_limit__".to_string());
73 params.insert("__core_page_limit__".to_string(), num_value(page.page_size));
74 }
75
76 if let Some(r) = ast.params.get("skip").cloned() {
78 let key = r.get(1..).unwrap_or("").to_string();
79 params.insert(key, num_value(page.page * page.page_size));
80 }
81 if let Some(r) = ast.params.get("limit").cloned() {
82 let key = r.get(1..).unwrap_or("").to_string();
83 params.insert(key, num_value(page.page_size));
84 }
85
86 let count_filter = ast
88 .params
89 .get("condition")
90 .and_then(|r| param(¶ms, Some(r)).cloned())
91 .filter(is_truthy)
92 .unwrap_or_else(|| json!({}));
93 let owner_merged =
95 merge_owner_condition(registry.role_rules(), schema, ctx, Some(count_filter));
96 let count_filter = merge_row_condition(registry, schema, ctx, "read", owner_merged)
97 .unwrap_or_else(|| json!({}));
98
99 if has_relation_predicate(schema, &count_filter) {
103 return Err(
104 "query_with_count 暂不支持关系聚合谓词条件(§9.6):total 无法与 items 保持一致,\
105 请改用 query 并在调用方自行统计"
106 .to_string(),
107 );
108 }
109
110 let query = plan_query_ast_mut(ast, &mut params, registry, ctx)?;
111
112 let count_command = cmd_count_documents(schema, &count_filter);
113
114 Ok(CountQueryPlan {
115 query,
116 page,
117 count_command,
118 })
119}