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 pub access_keys: Vec<super::access_keys::AccessKeyDef>,
217}
218
219impl Schema {
220 pub fn compute(&self, name: &str) -> Option<&ComputeDef> {
221 self.computes
222 .iter()
223 .find(|(k, _)| k == name)
224 .map(|(_, c)| c)
225 }
226
227 pub fn source(&self) -> &str {
229 self.source
230 .as_deref()
231 .unwrap_or(crate::datasource::DEFAULT_SOURCE)
232 }
233
234 pub fn database(&self) -> Option<&str> {
236 self.database.as_deref().filter(|s| !s.is_empty())
237 }
238
239 pub fn schema(&self) -> Option<&str> {
241 self.schema.as_deref().filter(|s| !s.is_empty())
242 }
243
244 pub fn location(&self) -> Location {
248 Location {
249 source: self.source().to_string(),
250 database: self.database().map(String::from),
251 schema: self.schema().map(String::from),
252 role: None,
253 priority: None,
254 }
255 }
256
257 pub fn id_is_autoincrement(&self) -> bool {
259 self.fields.get("_id").and_then(|f| f.strategy.as_deref()) == Some("autoincrement")
260 }
261
262 pub fn owner_key(&self) -> &str {
266 self.owner_key.as_deref().unwrap_or("createdBy")
267 }
268}
269
270pub(super) fn normalize_fields(v: Option<&Value>) -> Result<HashMap<String, FieldDef>, String> {
272 let mut out = HashMap::new();
273 let Some(Value::Object(map)) = v else {
274 return Ok(out);
275 };
276 for (key, val) in map {
277 let fd = if let Some(s) = val.as_str() {
278 FieldDef {
279 field_type: s.to_string(),
280 required: false,
281 default: None,
282 read: None,
283 write: None,
284 fields: None,
285 strategy: None,
286 dim: None,
287 metric: None,
288 }
289 } else if let Some(o) = val.as_object() {
290 let dim = match o.get("dim") {
292 None | Some(Value::Null) => None,
293 Some(Value::Number(n)) => Some(
294 n.as_u64()
295 .filter(|v| *v > 0 && *v <= u32::MAX as u64)
296 .map(|v| v as u32)
297 .ok_or_else(|| format!("字段 {key} 的 dim 必须是正整数,实际 {n}"))?,
298 ),
299 Some(other) => return Err(format!("字段 {key} 的 dim 必须是正整数,实际 {other}")),
300 };
301 let metric = match o.get("metric") {
302 None | Some(Value::Null) => None,
303 Some(Value::String(s)) if ["cosine", "l2", "ip"].contains(&s.as_str()) => {
304 Some(s.clone())
305 }
306 Some(other) => {
307 return Err(format!(
308 "字段 {key} 的 metric 仅支持 cosine/l2/ip,实际 {other}"
309 ))
310 }
311 };
312 FieldDef {
313 field_type: o
314 .get("type")
315 .and_then(|v| v.as_str())
316 .unwrap_or("")
317 .to_string(),
318 required: o.get("required").and_then(|v| v.as_bool()).unwrap_or(false),
319 default: o.get("default").filter(|v| !v.is_null()).cloned(),
320 read: str_list(o.get("read")),
321 write: str_list(o.get("write")),
322 fields: o.get("fields").filter(|v| is_truthy(v)).cloned(),
323 strategy: match o.get("strategy").and_then(|v| v.as_str()) {
324 None => None,
325 Some(v @ "autoincrement") => Some(v.to_string()),
326 Some(other) => {
328 return Err(format!(
329 "字段 {key} 的 strategy \"{other}\" 非法(仅支持 \"autoincrement\",且仅限 _id 字段)"
330 ))
331 }
332 },
333 dim,
334 metric,
335 }
336 } else {
337 return Err(format!("字段 {} 定义类型非法(须为类型字符串或对象)", key));
341 };
342 out.insert(key.clone(), fd);
343 }
344 Ok(out)
345}
346
347pub fn map_of(pairs: Vec<(&str, Value)>) -> Map<String, Value> {
349 let mut m = Map::new();
350 for (k, v) in pairs {
351 m.insert(k.to_string(), v);
352 }
353 m
354}