1use forgedb_validation::Position;
4
5#[derive(Debug, Clone, PartialEq)]
7pub enum ConstraintParam {
8 Number(i64),
9 String(String),
10}
11
12#[derive(Debug, Clone, PartialEq)]
14pub struct Constraint {
15 pub name: String,
16 pub params: Vec<ConstraintParam>,
17}
18
19impl Constraint {
20 pub fn new(name: String) -> Self {
21 Constraint {
22 name,
23 params: Vec::new(),
24 }
25 }
26
27 pub fn with_param(mut self, param: ConstraintParam) -> Self {
28 self.params.push(param);
29 self
30 }
31}
32
33#[derive(Debug, Clone, PartialEq)]
34pub enum IndexType {
35 Hash, BTree, }
38
39#[derive(Debug, Clone, PartialEq)]
40pub struct CompositeIndex {
41 pub fields: Vec<String>,
42}
43
44#[derive(Debug, Clone, PartialEq)]
50pub struct Projection {
51 pub name: String,
52 pub fields: Vec<String>,
53}
54
55#[derive(Debug, Clone, PartialEq)]
56pub enum FieldType {
57 U32,
58 U64,
59 I32,
60 I64,
61 F64,
62 Bool,
63 String,
64 Uuid,
65 Timestamp,
66 Json,
70 Decimal,
76 Enum(String),
84 Char(usize), FixedArray(Box<FieldType>, usize), StructType(String), OptionalStructType(String), Nullable(Box<FieldType>),
91 Relation(RelationType),
93 Component(ComponentReference),
95}
96
97#[derive(Debug, Clone, PartialEq)]
99pub enum ComponentProtocol {
100 Tsx, Jsx, Api, }
104
105#[derive(Debug, Clone, PartialEq)]
107pub enum RelationInclusion {
108 None,
109 All,
110 Specific(Vec<String>),
111}
112
113#[derive(Debug, Clone, PartialEq)]
115pub struct ComponentReference {
116 pub protocol: ComponentProtocol,
117 pub path: String,
118 pub relations: RelationInclusion,
119}
120
121#[derive(Debug, Clone, PartialEq)]
122pub enum RelationType {
123 OneToMany(String),
125 RequiredReference(String),
127 OptionalReference(String),
129 ManyToMany(String),
131}
132
133#[derive(Debug, Clone, PartialEq)]
134pub struct Field {
135 pub name: String,
136 pub field_type: FieldType,
137 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, pub position: Option<Position>,
148}
149
150#[derive(Debug, Clone, PartialEq)]
152pub struct Struct {
153 pub name: String,
154 pub fields: Vec<Field>,
155 pub position: Option<Position>,
157}
158
159#[derive(Debug, Clone, PartialEq)]
164pub struct EnumDef {
165 pub name: String,
166 pub variants: Vec<String>,
167 pub position: Option<Position>,
169}
170
171#[derive(Debug, Clone, PartialEq)]
172pub struct Model {
173 pub name: String,
174 pub fields: Vec<Field>,
175 pub composite_indexes: Vec<CompositeIndex>,
176 pub projections: Vec<Projection>, pub soft_delete: bool, pub position: Option<Position>,
180}
181
182#[derive(Debug, Clone, PartialEq)]
183pub struct Schema {
184 pub structs: Vec<Struct>, pub enums: Vec<EnumDef>, pub models: Vec<Model>,
187}
188
189impl Schema {
190 pub fn find_model(&self, name: &str) -> Option<&Model> {
192 self.models.iter().find(|m| m.name == name)
193 }
194
195 pub fn find_struct(&self, name: &str) -> Option<&Struct> {
197 self.structs.iter().find(|s| s.name == name)
198 }
199
200 pub fn find_enum(&self, name: &str) -> Option<&EnumDef> {
202 self.enums.iter().find(|e| e.name == name)
203 }
204
205 pub fn detect_relations(&self) -> Vec<RelationPair> {
213 let mut relations = Vec::new();
214
215 for model in &self.models {
216 for field in &model.fields {
217 if let FieldType::Relation(RelationType::OneToMany(target_model)) =
218 &field.field_type
219 {
220 if let Some(target) = self.find_model(target_model) {
222 for target_field in &target.fields {
224 if let FieldType::Relation(rel) = &target_field.field_type {
225 if rel.is_reference() && rel.target_model() == model.name {
226 relations.push(RelationPair {
227 parent_model: model.name.clone(),
228 parent_field: field.name.clone(),
229 child_model: target.name.clone(),
230 child_field: target_field.name.clone(),
231 is_required: matches!(
232 rel,
233 RelationType::RequiredReference(_)
234 ),
235 });
236 }
237 }
238 }
239 }
240 }
241 }
242 }
243
244 relations
245 }
246
247 pub fn detect_many_to_many_relations(&self) -> Vec<ManyToManyRelation> {
251 let mut m2m_relations = Vec::new();
252 let mut processed_pairs = std::collections::HashSet::new();
253
254 let one_to_many_field_pairs: std::collections::HashSet<(String, String, String, String)> =
257 self.detect_relations()
258 .iter()
259 .map(|rel| {
260 vec![
263 (
264 rel.parent_model.clone(),
265 rel.parent_field.clone(),
266 rel.child_model.clone(),
267 rel.child_field.clone(),
268 ),
269 (
270 rel.child_model.clone(),
271 rel.child_field.clone(),
272 rel.parent_model.clone(),
273 rel.parent_field.clone(),
274 ),
275 ]
276 })
277 .flatten()
278 .collect();
279
280 for model in &self.models {
281 for field in &model.fields {
282 if let FieldType::Relation(RelationType::OneToMany(target_model)) =
283 &field.field_type
284 {
285 if let Some(target) = self.find_model(target_model) {
287 for target_field in &target.fields {
288 if let FieldType::Relation(RelationType::OneToMany(back_ref)) =
289 &target_field.field_type
290 {
291 if back_ref == &model.name {
292 let is_one_to_many = one_to_many_field_pairs.contains(&(
295 model.name.clone(),
296 field.name.clone(),
297 target_model.clone(),
298 target_field.name.clone(),
299 ));
300
301 if !is_one_to_many {
302 let mut models_fields = vec![
305 (model.name.as_str(), field.name.as_str()),
306 (target.name.as_str(), target_field.name.as_str()),
307 ];
308 models_fields.sort();
309
310 let pair_key = format!(
311 "{}:{}:{}:{}",
312 models_fields[0].0,
313 models_fields[0].1,
314 models_fields[1].0,
315 models_fields[1].1
316 );
317
318 if !processed_pairs.contains(&pair_key) {
319 processed_pairs.insert(pair_key);
320
321 let (model1, field1, model2, field2) =
323 if model.name < target.name {
324 (
325 model.name.clone(),
326 field.name.clone(),
327 target.name.clone(),
328 target_field.name.clone(),
329 )
330 } else {
331 (
332 target.name.clone(),
333 target_field.name.clone(),
334 model.name.clone(),
335 field.name.clone(),
336 )
337 };
338
339 m2m_relations.push(ManyToManyRelation {
340 model1,
341 field1,
342 model2,
343 field2,
344 });
345 }
346 }
347 }
348 }
349 }
350 }
351 }
352 }
353 }
354
355 m2m_relations
356 }
357}
358
359#[derive(Debug, Clone, PartialEq)]
360pub struct RelationPair {
361 pub parent_model: String,
362 pub parent_field: String,
363 pub child_model: String,
364 pub child_field: String,
365 pub is_required: bool,
366}
367
368#[derive(Debug, Clone, PartialEq)]
370pub struct ManyToManyRelation {
371 pub model1: String,
372 pub field1: String,
373 pub model2: String,
374 pub field2: String,
375}
376
377impl FieldType {
378 pub fn to_rust_type(&self) -> String {
379 match self {
380 FieldType::U32 => "u32".to_string(),
381 FieldType::U64 => "u64".to_string(),
382 FieldType::I32 => "i32".to_string(),
383 FieldType::I64 => "i64".to_string(),
384 FieldType::F64 => "f64".to_string(),
385 FieldType::Bool => "bool".to_string(),
386 FieldType::String => "String".to_string(),
387 FieldType::Json => "serde_json::Value".to_string(),
388 FieldType::Decimal => "rust_decimal::Decimal".to_string(),
389 FieldType::Enum(name) => name.clone(),
390 FieldType::Uuid => "uuid::Uuid".to_string(),
391 FieldType::Timestamp => "i64".to_string(),
392 FieldType::Char(size) => format!("[u8; {}]", size),
393 FieldType::FixedArray(inner_type, count) => {
394 format!("[{}; {}]", inner_type.to_rust_type(), count)
395 }
396 FieldType::StructType(name) => name.clone(),
397 FieldType::OptionalStructType(name) => format!("Option<{}>", name),
398 FieldType::Nullable(inner) => format!("Option<{}>", inner.to_rust_type()),
399 FieldType::Relation(rel) => match rel {
400 RelationType::RequiredReference(model) => {
401 format!("uuid::Uuid /* FK to {} */", model)
402 }
403 RelationType::OptionalReference(model) => {
404 format!("Option<uuid::Uuid> /* FK to {} */", model)
405 }
406 RelationType::OneToMany(_) => "/* virtual field - no storage */".to_string(),
407 RelationType::ManyToMany(_) => {
408 "/* virtual field - stored in junction table */".to_string()
409 }
410 },
411 FieldType::Component(_) => "/* component reference - no storage */".to_string(),
412 }
413 }
414
415 pub fn is_auto_incrementable(&self) -> bool {
416 matches!(self, FieldType::U32 | FieldType::U64)
417 }
418
419 pub fn is_auto_generatable(&self) -> bool {
420 matches!(
421 self,
422 FieldType::U32 | FieldType::U64 | FieldType::Uuid | FieldType::Timestamp
423 )
424 }
425
426 pub fn is_relation(&self) -> bool {
427 matches!(self, FieldType::Relation(_))
428 }
429
430 pub fn is_fixed_size(&self) -> bool {
432 match self {
433 FieldType::U32
434 | FieldType::U64
435 | FieldType::I32
436 | FieldType::I64
437 | FieldType::F64
438 | FieldType::Bool
439 | FieldType::Uuid
440 | FieldType::Timestamp
441 | FieldType::Decimal | FieldType::Enum(_) | FieldType::Char(_) => true,
444 FieldType::FixedArray(inner, _) => inner.is_fixed_size(),
445 FieldType::StructType(_) => true, FieldType::OptionalStructType(_) => true, FieldType::Nullable(inner) => inner.is_fixed_size(),
448 FieldType::String => false,
449 FieldType::Json => false, FieldType::Relation(_) => false, FieldType::Component(_) => false, }
453 }
454
455 pub fn struct_name(&self) -> Option<&str> {
457 match self {
458 FieldType::StructType(name) => Some(name),
459 FieldType::OptionalStructType(name) => Some(name),
460 _ => None,
461 }
462 }
463
464 pub fn enum_name(&self) -> Option<&str> {
466 match self {
467 FieldType::Enum(name) => Some(name),
468 FieldType::Nullable(inner) => inner.enum_name(),
469 _ => None,
470 }
471 }
472
473 pub fn size_in_bytes(&self, schema: &Schema) -> usize {
475 match self {
476 FieldType::U32 | FieldType::I32 => 4,
477 FieldType::U64 | FieldType::I64 | FieldType::F64 | FieldType::Timestamp => 8,
478 FieldType::Bool => 1,
479 FieldType::Enum(_) => 1, FieldType::Uuid => 16,
481 FieldType::Char(size) => *size,
482 FieldType::FixedArray(inner, count) => inner.size_in_bytes(schema) * count,
483 FieldType::StructType(name) => {
484 if let Some(struct_def) = schema.find_struct(name) {
485 Struct::calculate_size(struct_def, schema)
486 } else {
487 0
488 }
489 }
490 FieldType::OptionalStructType(name) => {
491 1 + if let Some(struct_def) = schema.find_struct(name) {
493 Struct::calculate_size(struct_def, schema)
494 } else {
495 0
496 }
497 }
498 FieldType::Nullable(inner) => inner.size_in_bytes(schema),
500 _ => 0, }
502 }
503
504 pub fn alignment(&self, schema: &Schema) -> usize {
506 match self {
507 FieldType::U32 | FieldType::I32 => 4,
508 FieldType::U64 | FieldType::I64 | FieldType::F64 | FieldType::Timestamp => 8,
509 FieldType::Bool => 1,
510 FieldType::Enum(_) => 1, FieldType::Uuid => 16, FieldType::Char(_) => 1,
513 FieldType::FixedArray(inner, _) => inner.alignment(schema),
514 FieldType::StructType(name) => {
515 if let Some(struct_def) = schema.find_struct(name) {
516 Struct::calculate_alignment(struct_def, schema)
517 } else {
518 1
519 }
520 }
521 FieldType::OptionalStructType(name) => {
522 if let Some(struct_def) = schema.find_struct(name) {
523 Struct::calculate_alignment(struct_def, schema)
524 } else {
525 1
526 }
527 }
528 FieldType::Nullable(inner) => inner.alignment(schema),
529 _ => 1,
530 }
531 }
532
533 pub fn supports_range_queries(&self) -> bool {
535 matches!(
536 self,
537 FieldType::U32
538 | FieldType::U64
539 | FieldType::I32
540 | FieldType::I64
541 | FieldType::F64
542 | FieldType::Timestamp
543 )
544 }
545
546 pub fn default_index_type(&self) -> IndexType {
548 if self.supports_range_queries() {
549 IndexType::BTree
550 } else {
551 IndexType::Hash
552 }
553 }
554}
555
556impl Struct {
557 pub fn calculate_size(struct_def: &Struct, schema: &Schema) -> usize {
559 let mut size = 0;
560 let mut max_alignment = 1;
561
562 for field in &struct_def.fields {
563 let field_size = field.field_type.size_in_bytes(schema);
564 let field_align = field.field_type.alignment(schema);
565
566 max_alignment = max_alignment.max(field_align);
567
568 if size % field_align != 0 {
570 size += field_align - (size % field_align);
571 }
572
573 size += field_size;
574 }
575
576 if size % max_alignment != 0 {
578 size += max_alignment - (size % max_alignment);
579 }
580
581 size
582 }
583
584 pub fn calculate_alignment(struct_def: &Struct, schema: &Schema) -> usize {
586 struct_def
587 .fields
588 .iter()
589 .map(|f| f.field_type.alignment(schema))
590 .max()
591 .unwrap_or(1)
592 }
593}
594
595impl Field {
596 pub fn has_constraint(&self, name: &str) -> bool {
598 self.constraints.iter().any(|c| c.name == name)
599 }
600
601 pub fn get_constraint(&self, name: &str) -> Option<&Constraint> {
603 self.constraints.iter().find(|c| c.name == name)
604 }
605
606 pub fn is_nullable(&self) -> bool {
608 matches!(
609 &self.field_type,
610 FieldType::Relation(RelationType::OptionalReference(_))
611 | FieldType::OptionalStructType(_)
612 | FieldType::Nullable(_)
613 )
614 }
615}
616
617impl RelationType {
618 pub fn target_model(&self) -> &str {
619 match self {
620 RelationType::OneToMany(model) => model,
621 RelationType::RequiredReference(model) => model,
622 RelationType::OptionalReference(model) => model,
623 RelationType::ManyToMany(model) => model,
624 }
625 }
626
627 pub fn is_one_to_many(&self) -> bool {
628 matches!(self, RelationType::OneToMany(_))
629 }
630
631 pub fn is_reference(&self) -> bool {
632 matches!(
633 self,
634 RelationType::RequiredReference(_) | RelationType::OptionalReference(_)
635 )
636 }
637
638 pub fn is_many_to_many(&self) -> bool {
639 matches!(self, RelationType::ManyToMany(_))
640 }
641}