1use std::collections::HashMap;
4
5use serde_json::{Map, Value};
6
7use crate::datasource::DEFAULT_SOURCE;
8use crate::types::{is_truthy, str_list};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum Role {
14 Primary,
16 Replica,
18 Cache,
20 Archive,
22}
23
24impl Role {
25 pub fn parse(s: &str) -> Result<Role, String> {
27 match s {
28 "primary" => Ok(Role::Primary),
29 "replica" => Ok(Role::Replica),
30 "cache" => Ok(Role::Cache),
31 "archive" => Ok(Role::Archive),
32 other => Err(format!(
33 "未知链路角色: {other}(仅 primary/replica/cache/archive)"
34 )),
35 }
36 }
37
38 pub fn as_str(&self) -> &'static str {
39 match self {
40 Role::Primary => "primary",
41 Role::Replica => "replica",
42 Role::Cache => "cache",
43 Role::Archive => "archive",
44 }
45 }
46
47 pub fn rank(&self) -> u8 {
49 match self {
50 Role::Primary => 0,
51 Role::Replica => 1,
52 Role::Cache => 2,
53 Role::Archive => 3,
54 }
55 }
56}
57
58#[derive(Debug, Clone, PartialEq, Eq)]
62pub struct Location {
63 pub source: String,
65 pub database: Option<String>,
67 pub schema: Option<String>,
69 pub role: Option<Role>,
71 pub priority: Option<i64>,
73}
74
75impl Default for Location {
76 fn default() -> Self {
77 Self {
78 source: DEFAULT_SOURCE.to_string(),
79 database: None,
80 schema: None,
81 role: None,
82 priority: None,
83 }
84 }
85}
86
87impl Location {
88 pub fn same_place(&self, other: &Location) -> bool {
92 self.source == other.source
93 && self.database == other.database
94 && self.schema == other.schema
95 }
96}
97
98#[derive(Debug, Clone)]
99pub struct FieldDef {
100 pub field_type: String,
101 pub required: bool,
102 pub default: Option<Value>,
103 pub read: Option<Vec<String>>,
104 pub write: Option<Vec<String>>,
105 pub fields: Option<Value>,
107 pub strategy: Option<String>,
111 pub dim: Option<u32>,
113 pub metric: Option<String>,
115}
116
117impl FieldDef {
118 pub fn metric_or_default(&self) -> &str {
120 self.metric.as_deref().unwrap_or("cosine")
121 }
122}
123
124pub const MAX_VECTOR_DIM: u32 = 16000;
127
128pub(super) fn validate_vector_fields(fields: &HashMap<String, FieldDef>) -> Result<(), String> {
130 for (name, f) in fields {
131 let is_vector = f.field_type == "vector";
132 if is_vector {
133 let dim = match f.dim {
134 Some(d) => d,
135 None => {
136 return Err(format!(
137 "字段 {name} 类型为 vector,但缺少必填 dim(正整数)"
138 ))
139 }
140 };
141 if dim > MAX_VECTOR_DIM {
142 return Err(format!(
143 "字段 {name} 的 dim 超过上限:{dim} > {MAX_VECTOR_DIM}"
144 ));
145 }
146 } else {
147 if f.dim.is_some() {
148 return Err(format!(
149 "字段 {name} 类型为 {},不接受 dim(仅 vector 允许)",
150 f.field_type
151 ));
152 }
153 if f.metric.is_some() {
154 return Err(format!(
155 "字段 {name} 类型为 {},不接受 metric(仅 vector 允许)",
156 f.field_type
157 ));
158 }
159 }
160 }
161 Ok(())
162}
163
164#[derive(Debug, Clone)]
165pub struct RelationDef {
166 pub model: String,
167 pub rel_type: String,
168 pub local_field: String,
169 pub foreign_field: String,
170 pub read: Option<Vec<String>>,
171}
172
173#[derive(Debug, Clone)]
174pub struct ComputeDef {
175 pub comp_type: String,
176 pub has_fn: bool,
177 pub has_async_fn: bool,
178 pub fn_ref: Option<String>,
180 pub agg: Option<Value>,
183 pub depends: Vec<String>,
184 pub read: Option<Vec<String>>,
185}
186
187#[derive(Debug, Clone)]
188pub struct Schema {
189 pub name: String,
190 pub collection: String,
191 pub id_prefix: String,
192 pub timestamps: bool,
193 pub fields: HashMap<String, FieldDef>,
194 pub relations: HashMap<String, RelationDef>,
195 pub computes: Vec<(String, ComputeDef)>,
197 pub triggers: super::triggers::Triggers,
199 pub read: Option<Vec<String>>,
200 pub write: Option<Vec<String>>,
201 pub indexes: Vec<Value>,
203 pub source: Option<String>,
206 pub database: Option<String>,
209 pub schema: Option<String>,
211 pub owner_key: Option<String>,
214}
215
216impl Schema {
217 pub fn compute(&self, name: &str) -> Option<&ComputeDef> {
218 self.computes
219 .iter()
220 .find(|(k, _)| k == name)
221 .map(|(_, c)| c)
222 }
223
224 pub fn source(&self) -> &str {
226 self.source
227 .as_deref()
228 .unwrap_or(crate::datasource::DEFAULT_SOURCE)
229 }
230
231 pub fn database(&self) -> Option<&str> {
233 self.database.as_deref().filter(|s| !s.is_empty())
234 }
235
236 pub fn schema(&self) -> Option<&str> {
238 self.schema.as_deref().filter(|s| !s.is_empty())
239 }
240
241 pub fn location(&self) -> Location {
245 Location {
246 source: self.source().to_string(),
247 database: self.database().map(String::from),
248 schema: self.schema().map(String::from),
249 role: None,
250 priority: None,
251 }
252 }
253
254 pub fn id_is_autoincrement(&self) -> bool {
256 self.fields.get("_id").and_then(|f| f.strategy.as_deref()) == Some("autoincrement")
257 }
258
259 pub fn owner_key(&self) -> &str {
263 self.owner_key.as_deref().unwrap_or("createdBy")
264 }
265}
266
267pub(super) fn normalize_fields(v: Option<&Value>) -> Result<HashMap<String, FieldDef>, String> {
269 let mut out = HashMap::new();
270 let Some(Value::Object(map)) = v else {
271 return Ok(out);
272 };
273 for (key, val) in map {
274 let fd = if let Some(s) = val.as_str() {
275 FieldDef {
276 field_type: s.to_string(),
277 required: false,
278 default: None,
279 read: None,
280 write: None,
281 fields: None,
282 strategy: None,
283 dim: None,
284 metric: None,
285 }
286 } else if let Some(o) = val.as_object() {
287 let dim = match o.get("dim") {
289 None | Some(Value::Null) => None,
290 Some(Value::Number(n)) => Some(
291 n.as_u64()
292 .filter(|v| *v > 0 && *v <= u32::MAX as u64)
293 .map(|v| v as u32)
294 .ok_or_else(|| format!("字段 {key} 的 dim 必须是正整数,实际 {n}"))?,
295 ),
296 Some(other) => return Err(format!("字段 {key} 的 dim 必须是正整数,实际 {other}")),
297 };
298 let metric = match o.get("metric") {
299 None | Some(Value::Null) => None,
300 Some(Value::String(s)) if ["cosine", "l2", "ip"].contains(&s.as_str()) => {
301 Some(s.clone())
302 }
303 Some(other) => {
304 return Err(format!(
305 "字段 {key} 的 metric 仅支持 cosine/l2/ip,实际 {other}"
306 ))
307 }
308 };
309 FieldDef {
310 field_type: o
311 .get("type")
312 .and_then(|v| v.as_str())
313 .unwrap_or("")
314 .to_string(),
315 required: o.get("required").and_then(|v| v.as_bool()).unwrap_or(false),
316 default: o.get("default").filter(|v| !v.is_null()).cloned(),
317 read: str_list(o.get("read")),
318 write: str_list(o.get("write")),
319 fields: o.get("fields").filter(|v| is_truthy(v)).cloned(),
320 strategy: match o.get("strategy").and_then(|v| v.as_str()) {
321 None => None,
322 Some(v @ "autoincrement") => Some(v.to_string()),
323 Some(other) => {
325 return Err(format!(
326 "字段 {key} 的 strategy \"{other}\" 非法(仅支持 \"autoincrement\",且仅限 _id 字段)"
327 ))
328 }
329 },
330 dim,
331 metric,
332 }
333 } else {
334 return Err(format!("字段 {} 定义类型非法(须为类型字符串或对象)", key));
338 };
339 out.insert(key.clone(), fd);
340 }
341 Ok(out)
342}
343
344pub fn map_of(pairs: Vec<(&str, Value)>) -> Map<String, Value> {
346 let mut m = Map::new();
347 for (k, v) in pairs {
348 m.insert(k.to_string(), v);
349 }
350 m
351}