1#[derive(Debug, Clone, PartialEq)]
5pub enum ConstraintParam {
6 Number(i64),
7 String(String),
8}
9
10#[derive(Debug, Clone, PartialEq)]
12pub struct Constraint {
13 pub name: String,
14 pub params: Vec<ConstraintParam>,
15}
16
17impl Constraint {
18 pub fn new(name: String) -> Self {
19 Constraint {
20 name,
21 params: Vec::new(),
22 }
23 }
24
25 pub fn with_param(mut self, param: ConstraintParam) -> Self {
26 self.params.push(param);
27 self
28 }
29}
30
31#[derive(Debug, Clone, PartialEq)]
32pub enum IndexType {
33 Hash, BTree, }
36
37#[derive(Debug, Clone, PartialEq)]
38pub struct CompositeIndex {
39 pub fields: Vec<String>,
40}
41
42#[derive(Debug, Clone, PartialEq)]
43pub enum FieldType {
44 U32,
45 U64,
46 I32,
47 I64,
48 F64,
49 Bool,
50 String,
51 Uuid,
52 Timestamp,
53 Char(usize), FixedArray(Box<FieldType>, usize), StructType(String), OptionalStructType(String), Nullable(Box<FieldType>),
60 Relation(RelationType),
62 Component(ComponentReference),
64}
65
66#[derive(Debug, Clone, PartialEq)]
68pub enum ComponentProtocol {
69 Tsx, Jsx, Api, }
73
74#[derive(Debug, Clone, PartialEq)]
76pub enum RelationInclusion {
77 None,
78 All,
79 Specific(Vec<String>),
80}
81
82#[derive(Debug, Clone, PartialEq)]
84pub struct ComponentReference {
85 pub protocol: ComponentProtocol,
86 pub path: String,
87 pub relations: RelationInclusion,
88}
89
90#[derive(Debug, Clone, PartialEq)]
91pub enum RelationType {
92 OneToMany(String),
94 RequiredReference(String),
96 OptionalReference(String),
98 ManyToMany(String),
100}
101
102#[derive(Debug, Clone, PartialEq)]
103pub struct Field {
104 pub name: String,
105 pub field_type: FieldType,
106 pub auto_generate: bool, pub unique: bool, pub indexed: bool, pub constraints: Vec<Constraint>, pub index_type: IndexType, pub is_computed: bool, pub fulltext_indexed: bool, pub is_materialized: bool, }
115
116#[derive(Debug, Clone, PartialEq)]
118pub struct Struct {
119 pub name: String,
120 pub fields: Vec<Field>,
121}
122
123#[derive(Debug, Clone, PartialEq)]
124pub struct Model {
125 pub name: String,
126 pub fields: Vec<Field>,
127 pub composite_indexes: Vec<CompositeIndex>,
128 pub soft_delete: bool, }
130
131#[derive(Debug, Clone, PartialEq)]
132pub struct Schema {
133 pub structs: Vec<Struct>, pub models: Vec<Model>,
135}
136
137impl Schema {
138 pub fn find_model(&self, name: &str) -> Option<&Model> {
140 self.models.iter().find(|m| m.name == name)
141 }
142
143 pub fn find_struct(&self, name: &str) -> Option<&Struct> {
145 self.structs.iter().find(|s| s.name == name)
146 }
147
148 pub fn validate_relations(&self) -> Result<(), String> {
150 for model in &self.models {
151 for field in &model.fields {
152 if let FieldType::Relation(rel_type) = &field.field_type {
153 let target = rel_type.target_model();
154 if self.find_model(target).is_none() {
156 return Err(format!(
157 "Model '{}' field '{}' references undefined model '{}'",
158 model.name, field.name, target
159 ));
160 }
161 }
162 }
163 }
164 Ok(())
165 }
166
167 pub fn validate_struct_references(&self) -> Result<(), String> {
169 for struct_def in &self.structs {
171 for field in &struct_def.fields {
172 if !field.field_type.is_fixed_size() {
173 return Err(format!(
174 "Struct '{}' field '{}' contains variable-length type. Structs can only contain fixed-size types.",
175 struct_def.name, field.name
176 ));
177 }
178 }
179 }
180
181 for model in &self.models {
183 for field in &model.fields {
184 if let Some(struct_name) = field.field_type.struct_name() {
185 if self.find_struct(struct_name).is_none() {
186 return Err(format!(
187 "Model '{}' field '{}' references undefined struct '{}'",
188 model.name, field.name, struct_name
189 ));
190 }
191 }
192 }
193 }
194 Ok(())
195 }
196
197 pub fn detect_relations(&self) -> Vec<RelationPair> {
199 let mut relations = Vec::new();
200
201 for model in &self.models {
202 for field in &model.fields {
203 if let FieldType::Relation(RelationType::OneToMany(target_model)) =
204 &field.field_type
205 {
206 if let Some(target) = self.find_model(target_model) {
208 for target_field in &target.fields {
210 if let FieldType::Relation(rel) = &target_field.field_type {
211 if rel.is_reference() && rel.target_model() == model.name {
212 relations.push(RelationPair {
213 parent_model: model.name.clone(),
214 parent_field: field.name.clone(),
215 child_model: target.name.clone(),
216 child_field: target_field.name.clone(),
217 is_required: matches!(
218 rel,
219 RelationType::RequiredReference(_)
220 ),
221 });
222 }
223 }
224 }
225 }
226 }
227 }
228 }
229
230 relations
231 }
232
233 pub fn detect_many_to_many_relations(&self) -> Vec<ManyToManyRelation> {
237 let mut m2m_relations = Vec::new();
238 let mut processed_pairs = std::collections::HashSet::new();
239
240 let one_to_many_field_pairs: std::collections::HashSet<(String, String, String, String)> =
243 self.detect_relations()
244 .iter()
245 .map(|rel| {
246 vec![
249 (
250 rel.parent_model.clone(),
251 rel.parent_field.clone(),
252 rel.child_model.clone(),
253 rel.child_field.clone(),
254 ),
255 (
256 rel.child_model.clone(),
257 rel.child_field.clone(),
258 rel.parent_model.clone(),
259 rel.parent_field.clone(),
260 ),
261 ]
262 })
263 .flatten()
264 .collect();
265
266 for model in &self.models {
267 for field in &model.fields {
268 if let FieldType::Relation(RelationType::OneToMany(target_model)) =
269 &field.field_type
270 {
271 if let Some(target) = self.find_model(target_model) {
273 for target_field in &target.fields {
274 if let FieldType::Relation(RelationType::OneToMany(back_ref)) =
275 &target_field.field_type
276 {
277 if back_ref == &model.name {
278 let is_one_to_many = one_to_many_field_pairs.contains(&(
281 model.name.clone(),
282 field.name.clone(),
283 target_model.clone(),
284 target_field.name.clone(),
285 ));
286
287 if !is_one_to_many {
288 let mut models_fields = vec![
291 (model.name.as_str(), field.name.as_str()),
292 (target.name.as_str(), target_field.name.as_str()),
293 ];
294 models_fields.sort();
295
296 let pair_key = format!(
297 "{}:{}:{}:{}",
298 models_fields[0].0,
299 models_fields[0].1,
300 models_fields[1].0,
301 models_fields[1].1
302 );
303
304 if !processed_pairs.contains(&pair_key) {
305 processed_pairs.insert(pair_key);
306
307 let (model1, field1, model2, field2) =
309 if model.name < target.name {
310 (
311 model.name.clone(),
312 field.name.clone(),
313 target.name.clone(),
314 target_field.name.clone(),
315 )
316 } else {
317 (
318 target.name.clone(),
319 target_field.name.clone(),
320 model.name.clone(),
321 field.name.clone(),
322 )
323 };
324
325 m2m_relations.push(ManyToManyRelation {
326 model1,
327 field1,
328 model2,
329 field2,
330 });
331 }
332 }
333 }
334 }
335 }
336 }
337 }
338 }
339 }
340
341 m2m_relations
342 }
343}
344
345#[derive(Debug, Clone, PartialEq)]
346pub struct RelationPair {
347 pub parent_model: String,
348 pub parent_field: String,
349 pub child_model: String,
350 pub child_field: String,
351 pub is_required: bool,
352}
353
354#[derive(Debug, Clone, PartialEq)]
356pub struct ManyToManyRelation {
357 pub model1: String,
358 pub field1: String,
359 pub model2: String,
360 pub field2: String,
361}
362
363impl FieldType {
364 pub fn to_rust_type(&self) -> String {
365 match self {
366 FieldType::U32 => "u32".to_string(),
367 FieldType::U64 => "u64".to_string(),
368 FieldType::I32 => "i32".to_string(),
369 FieldType::I64 => "i64".to_string(),
370 FieldType::F64 => "f64".to_string(),
371 FieldType::Bool => "bool".to_string(),
372 FieldType::String => "String".to_string(),
373 FieldType::Uuid => "uuid::Uuid".to_string(),
374 FieldType::Timestamp => "i64".to_string(),
375 FieldType::Char(size) => format!("[u8; {}]", size),
376 FieldType::FixedArray(inner_type, count) => {
377 format!("[{}; {}]", inner_type.to_rust_type(), count)
378 }
379 FieldType::StructType(name) => name.clone(),
380 FieldType::OptionalStructType(name) => format!("Option<{}>", name),
381 FieldType::Nullable(inner) => format!("Option<{}>", inner.to_rust_type()),
382 FieldType::Relation(rel) => match rel {
383 RelationType::RequiredReference(model) => {
384 format!("uuid::Uuid /* FK to {} */", model)
385 }
386 RelationType::OptionalReference(model) => {
387 format!("Option<uuid::Uuid> /* FK to {} */", model)
388 }
389 RelationType::OneToMany(_) => "/* virtual field - no storage */".to_string(),
390 RelationType::ManyToMany(_) => {
391 "/* virtual field - stored in junction table */".to_string()
392 }
393 },
394 FieldType::Component(_) => "/* component reference - no storage */".to_string(),
395 }
396 }
397
398 pub fn is_auto_incrementable(&self) -> bool {
399 matches!(self, FieldType::U32 | FieldType::U64)
400 }
401
402 pub fn is_auto_generatable(&self) -> bool {
403 matches!(
404 self,
405 FieldType::U32 | FieldType::U64 | FieldType::Uuid | FieldType::Timestamp
406 )
407 }
408
409 pub fn is_relation(&self) -> bool {
410 matches!(self, FieldType::Relation(_))
411 }
412
413 pub fn is_fixed_size(&self) -> bool {
415 match self {
416 FieldType::U32
417 | FieldType::U64
418 | FieldType::I32
419 | FieldType::I64
420 | FieldType::F64
421 | FieldType::Bool
422 | FieldType::Uuid
423 | FieldType::Timestamp
424 | FieldType::Char(_) => true,
425 FieldType::FixedArray(inner, _) => inner.is_fixed_size(),
426 FieldType::StructType(_) => true, FieldType::OptionalStructType(_) => true, FieldType::Nullable(inner) => inner.is_fixed_size(),
429 FieldType::String => false,
430 FieldType::Relation(_) => false, FieldType::Component(_) => false, }
433 }
434
435 pub fn struct_name(&self) -> Option<&str> {
437 match self {
438 FieldType::StructType(name) => Some(name),
439 FieldType::OptionalStructType(name) => Some(name),
440 _ => None,
441 }
442 }
443
444 pub fn size_in_bytes(&self, schema: &Schema) -> usize {
446 match self {
447 FieldType::U32 | FieldType::I32 => 4,
448 FieldType::U64 | FieldType::I64 | FieldType::F64 | FieldType::Timestamp => 8,
449 FieldType::Bool => 1,
450 FieldType::Uuid => 16,
451 FieldType::Char(size) => *size,
452 FieldType::FixedArray(inner, count) => inner.size_in_bytes(schema) * count,
453 FieldType::StructType(name) => {
454 if let Some(struct_def) = schema.find_struct(name) {
455 Struct::calculate_size(struct_def, schema)
456 } else {
457 0
458 }
459 }
460 FieldType::OptionalStructType(name) => {
461 1 + if let Some(struct_def) = schema.find_struct(name) {
463 Struct::calculate_size(struct_def, schema)
464 } else {
465 0
466 }
467 }
468 FieldType::Nullable(inner) => inner.size_in_bytes(schema),
470 _ => 0, }
472 }
473
474 pub fn alignment(&self, schema: &Schema) -> usize {
476 match self {
477 FieldType::U32 | FieldType::I32 => 4,
478 FieldType::U64 | FieldType::I64 | FieldType::F64 | FieldType::Timestamp => 8,
479 FieldType::Bool => 1,
480 FieldType::Uuid => 16, FieldType::Char(_) => 1,
482 FieldType::FixedArray(inner, _) => inner.alignment(schema),
483 FieldType::StructType(name) => {
484 if let Some(struct_def) = schema.find_struct(name) {
485 Struct::calculate_alignment(struct_def, schema)
486 } else {
487 1
488 }
489 }
490 FieldType::OptionalStructType(name) => {
491 if let Some(struct_def) = schema.find_struct(name) {
492 Struct::calculate_alignment(struct_def, schema)
493 } else {
494 1
495 }
496 }
497 FieldType::Nullable(inner) => inner.alignment(schema),
498 _ => 1,
499 }
500 }
501
502 pub fn supports_range_queries(&self) -> bool {
504 matches!(
505 self,
506 FieldType::U32
507 | FieldType::U64
508 | FieldType::I32
509 | FieldType::I64
510 | FieldType::F64
511 | FieldType::Timestamp
512 )
513 }
514
515 pub fn default_index_type(&self) -> IndexType {
517 if self.supports_range_queries() {
518 IndexType::BTree
519 } else {
520 IndexType::Hash
521 }
522 }
523}
524
525impl Struct {
526 pub fn calculate_size(struct_def: &Struct, schema: &Schema) -> usize {
528 let mut size = 0;
529 let mut max_alignment = 1;
530
531 for field in &struct_def.fields {
532 let field_size = field.field_type.size_in_bytes(schema);
533 let field_align = field.field_type.alignment(schema);
534
535 max_alignment = max_alignment.max(field_align);
536
537 if size % field_align != 0 {
539 size += field_align - (size % field_align);
540 }
541
542 size += field_size;
543 }
544
545 if size % max_alignment != 0 {
547 size += max_alignment - (size % max_alignment);
548 }
549
550 size
551 }
552
553 pub fn calculate_alignment(struct_def: &Struct, schema: &Schema) -> usize {
555 struct_def
556 .fields
557 .iter()
558 .map(|f| f.field_type.alignment(schema))
559 .max()
560 .unwrap_or(1)
561 }
562}
563
564impl Field {
565 pub fn has_constraint(&self, name: &str) -> bool {
567 self.constraints.iter().any(|c| c.name == name)
568 }
569
570 pub fn get_constraint(&self, name: &str) -> Option<&Constraint> {
572 self.constraints.iter().find(|c| c.name == name)
573 }
574
575 pub fn is_nullable(&self) -> bool {
577 matches!(
578 &self.field_type,
579 FieldType::Relation(RelationType::OptionalReference(_))
580 | FieldType::OptionalStructType(_)
581 | FieldType::Nullable(_)
582 )
583 }
584}
585
586impl RelationType {
587 pub fn target_model(&self) -> &str {
588 match self {
589 RelationType::OneToMany(model) => model,
590 RelationType::RequiredReference(model) => model,
591 RelationType::OptionalReference(model) => model,
592 RelationType::ManyToMany(model) => model,
593 }
594 }
595
596 pub fn is_one_to_many(&self) -> bool {
597 matches!(self, RelationType::OneToMany(_))
598 }
599
600 pub fn is_reference(&self) -> bool {
601 matches!(
602 self,
603 RelationType::RequiredReference(_) | RelationType::OptionalReference(_)
604 )
605 }
606
607 pub fn is_many_to_many(&self) -> bool {
608 matches!(self, RelationType::ManyToMany(_))
609 }
610}