Skip to main content

rust_store_core/schema/
definition.rs

1//! Schema 的数据结构与字段规范化(`Registry` 见 [`super::registry`])。
2
3use std::collections::HashMap;
4
5use serde_json::{Map, Value};
6
7use crate::datasource::DEFAULT_SOURCE;
8use crate::types::{is_truthy, str_list};
9
10/// 落点:定义之外,由「目录语义 + 连接配置」解析得到(设计 §5.2)。
11/// 落点维度 = `source` + `database` + `schema`(`schema` 仅 PostgreSQL)。
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct Location {
14    /// 连接选择(source 名);缺省 [`DEFAULT_SOURCE`]
15    pub source: String,
16    /// 连接内「库」:Mongo db / MySQL database / SQLite attached / PG 库
17    pub database: Option<String>,
18    /// 连接内「schema」:仅 PostgreSQL
19    pub schema: Option<String>,
20}
21
22impl Default for Location {
23    fn default() -> Self {
24        Self {
25            source: DEFAULT_SOURCE.to_string(),
26            database: None,
27            schema: None,
28        }
29    }
30}
31
32#[derive(Debug, Clone)]
33pub struct FieldDef {
34    pub field_type: String,
35    pub required: bool,
36    pub default: Option<Value>,
37    pub read: Option<Vec<String>>,
38    pub write: Option<Vec<String>>,
39    /// 嵌套 object 字段的原始定义(未规范化,与 JS 保持一致)
40    pub fields: Option<Value>,
41    /// ID 供给策略(阶段2):仅 `_id` 字段有意义 —— `"autoincrement"` = 数据库自增列
42    /// 赋值(SQL 后端),宿主不生成、core 不注入 `_id`;Mongo 后端无自增语义,
43    /// 执行器对此显式报错(AUTOINCREMENT_NOT_SUPPORTED)。其余值非法(注册期校验)。
44    pub strategy: Option<String>,
45}
46
47#[derive(Debug, Clone)]
48pub struct RelationDef {
49    pub model: String,
50    pub rel_type: String,
51    pub local_field: String,
52    pub foreign_field: String,
53    pub read: Option<Vec<String>>,
54}
55
56#[derive(Debug, Clone)]
57pub struct ComputeDef {
58    pub comp_type: String,
59    pub has_fn: bool,
60    pub has_async_fn: bool,
61    /// 回调标识:Host 经 FnRegistry 把它绑定到实现;缺省 = 计算列 key 名
62    pub fn_ref: Option<String>,
63    /// 归一聚合算子(§9.2(2)):`{"$count": "lessons"}` / `{"$sum": "lessons.duration"}`。
64    /// 取代旧的 `lookup` + `addFields` Mongo 专用形态;SQL / Mongo 走同一语法(执行下推优先)。
65    pub agg: Option<Value>,
66    pub depends: Vec<String>,
67    pub read: Option<Vec<String>>,
68}
69
70#[derive(Debug, Clone)]
71pub struct Schema {
72    pub name: String,
73    pub collection: String,
74    pub id_prefix: String,
75    pub timestamps: bool,
76    pub fields: HashMap<String, FieldDef>,
77    pub relations: HashMap<String, RelationDef>,
78    /// 保持注册顺序:`build_agg_stages` 的输出顺序依赖它
79    pub computes: Vec<(String, ComputeDef)>,
80    /// 写触发器声明(事件 → 触发列表;解析见 `super::triggers`,规划见 `crate::command::triggers`)
81    pub triggers: super::triggers::Triggers,
82    pub read: Option<Vec<String>>,
83    pub write: Option<Vec<String>>,
84    /// 原始索引定义(`[{keys: {...}, options: {...}}]`),upsert 条件构建依赖 unique 索引
85    pub indexes: Vec<Value>,
86    /// 数据源名(由 `register_batch` 的 [`Location`] 注入;`None` = 缺省 `default`)。
87    /// 见 [`crate::datasource`]
88    pub source: Option<String>,
89    /// 连接内「库」(由 `register_batch` 注入;`None` = 连接默认:Mongo db 实例的库名、
90    /// PG 的库、MySQL 的连接库、SQLite 的 main)。
91    pub database: Option<String>,
92    /// 连接内「schema」(仅 PG;由 `register_batch` 注入;`None` = 连接默认 search_path)。
93    pub schema: Option<String>,
94}
95
96impl Schema {
97    pub fn compute(&self, name: &str) -> Option<&ComputeDef> {
98        self.computes
99            .iter()
100            .find(|(k, _)| k == name)
101            .map(|(_, c)| c)
102    }
103
104    /// 解析后的数据源名(缺省 `default`)
105    pub fn source(&self) -> &str {
106        self.source
107            .as_deref()
108            .unwrap_or(crate::datasource::DEFAULT_SOURCE)
109    }
110
111    /// 解析后的 database(空串归一为 `None`)
112    pub fn database(&self) -> Option<&str> {
113        self.database.as_deref().filter(|s| !s.is_empty())
114    }
115
116    /// 解析后的 schema(空串归一为 `None`;仅 PG)
117    pub fn schema(&self) -> Option<&str> {
118        self.schema.as_deref().filter(|s| !s.is_empty())
119    }
120
121    /// 主落点(由 `source`/`database`/`schema` 组装)。
122    ///
123    /// 等价于 `Registry::primary_location(name)` 的投影;链路缺失时的回落。
124    pub fn location(&self) -> Location {
125        Location {
126            source: self.source().to_string(),
127            database: self.database().map(String::from),
128            schema: self.schema().map(String::from),
129        }
130    }
131
132    /// `_id` 是否声明 `strategy: "autoincrement"`(阶段2:数据库自增主键)
133    pub fn id_is_autoincrement(&self) -> bool {
134        self.fields.get("_id").and_then(|f| f.strategy.as_deref()) == Some("autoincrement")
135    }
136}
137
138/// 规范化 fields 定义(字符串简写 → `{type, required:false}`)
139pub(super) fn normalize_fields(v: Option<&Value>) -> Result<HashMap<String, FieldDef>, String> {
140    let mut out = HashMap::new();
141    let Some(Value::Object(map)) = v else {
142        return Ok(out);
143    };
144    for (key, val) in map {
145        let fd = if let Some(s) = val.as_str() {
146            FieldDef {
147                field_type: s.to_string(),
148                required: false,
149                default: None,
150                read: None,
151                write: None,
152                fields: None,
153                strategy: None,
154            }
155        } else if let Some(o) = val.as_object() {
156            FieldDef {
157                field_type: o
158                    .get("type")
159                    .and_then(|v| v.as_str())
160                    .unwrap_or("")
161                    .to_string(),
162                required: o.get("required").and_then(|v| v.as_bool()).unwrap_or(false),
163                default: o.get("default").filter(|v| !v.is_null()).cloned(),
164                read: str_list(o.get("read")),
165                write: str_list(o.get("write")),
166                fields: o.get("fields").filter(|v| is_truthy(v)).cloned(),
167                strategy: match o.get("strategy").and_then(|v| v.as_str()) {
168                    None => None,
169                    Some(v @ "autoincrement") => Some(v.to_string()),
170                    // no-error-masking:未知策略显式拒绝,禁止静默吞掉后按默认行为跑
171                    Some(other) => {
172                        return Err(format!(
173                            "字段 {key} 的 strategy \"{other}\" 非法(仅支持 \"autoincrement\",且仅限 _id 字段)"
174                        ))
175                    }
176                },
177            }
178        } else {
179            // 非字符串、非对象的定义(如 `fields: { price: 123 }`)= 脏 schema,
180            // fail-fast 报错而非静默产出空 field_type(后续 object/array 展平、
181            // 标量列判定都会走错且无信号)
182            return Err(format!("字段 {} 定义类型非法(须为类型字符串或对象)", key));
183        };
184        out.insert(key.clone(), fd);
185    }
186    Ok(out)
187}
188
189/// 便捷构造:`Map` from `[(&str, Value)]`
190pub fn map_of(pairs: Vec<(&str, Value)>) -> Map<String, Value> {
191    let mut m = Map::new();
192    for (k, v) in pairs {
193        m.insert(k.to_string(), v);
194    }
195    m
196}