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