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