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    pub read: Option<Vec<String>>,
81    pub write: Option<Vec<String>>,
82    /// 原始索引定义(`[{keys: {...}, options: {...}}]`),upsert 条件构建依赖 unique 索引
83    pub indexes: Vec<Value>,
84    /// 数据源名(由 `register_batch` 的 [`Location`] 注入;`None` = 缺省 `default`)。
85    /// 见 [`crate::datasource`]
86    pub source: Option<String>,
87    /// 连接内「库」(由 `register_batch` 注入;`None` = 连接默认:Mongo db 实例的库名、
88    /// PG 的库、MySQL 的连接库、SQLite 的 main)。
89    pub database: Option<String>,
90    /// 连接内「schema」(仅 PG;由 `register_batch` 注入;`None` = 连接默认 search_path)。
91    pub schema: Option<String>,
92}
93
94impl Schema {
95    pub fn compute(&self, name: &str) -> Option<&ComputeDef> {
96        self.computes
97            .iter()
98            .find(|(k, _)| k == name)
99            .map(|(_, c)| c)
100    }
101
102    /// 解析后的数据源名(缺省 `default`)
103    pub fn source(&self) -> &str {
104        self.source
105            .as_deref()
106            .unwrap_or(crate::datasource::DEFAULT_SOURCE)
107    }
108
109    /// 解析后的 database(空串归一为 `None`)
110    pub fn database(&self) -> Option<&str> {
111        self.database.as_deref().filter(|s| !s.is_empty())
112    }
113
114    /// 解析后的 schema(空串归一为 `None`;仅 PG)
115    pub fn schema(&self) -> Option<&str> {
116        self.schema.as_deref().filter(|s| !s.is_empty())
117    }
118
119    /// 主落点(由 `source`/`database`/`schema` 组装)。
120    ///
121    /// 等价于 `Registry::primary_location(name)` 的投影;链路缺失时的回落。
122    pub fn location(&self) -> Location {
123        Location {
124            source: self.source().to_string(),
125            database: self.database().map(String::from),
126            schema: self.schema().map(String::from),
127        }
128    }
129
130    /// `_id` 是否声明 `strategy: "autoincrement"`(阶段2:数据库自增主键)
131    pub fn id_is_autoincrement(&self) -> bool {
132        self.fields.get("_id").and_then(|f| f.strategy.as_deref()) == Some("autoincrement")
133    }
134}
135
136/// 规范化 fields 定义(字符串简写 → `{type, required:false}`)
137pub(super) fn normalize_fields(v: Option<&Value>) -> Result<HashMap<String, FieldDef>, String> {
138    let mut out = HashMap::new();
139    let Some(Value::Object(map)) = v else {
140        return Ok(out);
141    };
142    for (key, val) in map {
143        let fd = if let Some(s) = val.as_str() {
144            FieldDef {
145                field_type: s.to_string(),
146                required: false,
147                default: None,
148                read: None,
149                write: None,
150                fields: None,
151                strategy: None,
152            }
153        } else if let Some(o) = val.as_object() {
154            FieldDef {
155                field_type: o
156                    .get("type")
157                    .and_then(|v| v.as_str())
158                    .unwrap_or("")
159                    .to_string(),
160                required: o.get("required").and_then(|v| v.as_bool()).unwrap_or(false),
161                default: o.get("default").filter(|v| !v.is_null()).cloned(),
162                read: str_list(o.get("read")),
163                write: str_list(o.get("write")),
164                fields: o.get("fields").filter(|v| is_truthy(v)).cloned(),
165                strategy: match o.get("strategy").and_then(|v| v.as_str()) {
166                    None => None,
167                    Some(v @ "autoincrement") => Some(v.to_string()),
168                    // no-error-masking:未知策略显式拒绝,禁止静默吞掉后按默认行为跑
169                    Some(other) => {
170                        return Err(format!(
171                            "字段 {key} 的 strategy \"{other}\" 非法(仅支持 \"autoincrement\",且仅限 _id 字段)"
172                        ))
173                    }
174                },
175            }
176        } else {
177            // 非字符串、非对象的定义(如 `fields: { price: 123 }`)= 脏 schema,
178            // fail-fast 报错而非静默产出空 field_type(后续 object/array 展平、
179            // 标量列判定都会走错且无信号)
180            return Err(format!("字段 {} 定义类型非法(须为类型字符串或对象)", key));
181        };
182        out.insert(key.clone(), fd);
183    }
184    Ok(out)
185}
186
187/// 便捷构造:`Map` from `[(&str, Value)]`
188pub fn map_of(pairs: Vec<(&str, Value)>) -> Map<String, Value> {
189    let mut m = Map::new();
190    for (k, v) in pairs {
191        m.insert(k.to_string(), v);
192    }
193    m
194}