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::Decimal => 16, FieldType::Char(size) => *size,
483 FieldType::FixedArray(inner, count) => inner.size_in_bytes(schema) * count,
484 FieldType::StructType(name) => {
485 if let Some(struct_def) = schema.find_struct(name) {
486 Struct::calculate_size(struct_def, schema)
487 } else {
488 0
489 }
490 }
491 FieldType::OptionalStructType(name) => {
492 1 + if let Some(struct_def) = schema.find_struct(name) {
494 Struct::calculate_size(struct_def, schema)
495 } else {
496 0
497 }
498 }
499 FieldType::Nullable(inner) => inner.size_in_bytes(schema),
501 _ => 0, }
503 }
504
505 pub fn alignment(&self, schema: &Schema) -> usize {
507 match self {
508 FieldType::U32 | FieldType::I32 => 4,
509 FieldType::U64 | FieldType::I64 | FieldType::F64 | FieldType::Timestamp => 8,
510 FieldType::Bool => 1,
511 FieldType::Enum(_) => 1, FieldType::Uuid => 16, FieldType::Char(_) => 1,
514 FieldType::FixedArray(inner, _) => inner.alignment(schema),
515 FieldType::StructType(name) => {
516 if let Some(struct_def) = schema.find_struct(name) {
517 Struct::calculate_alignment(struct_def, schema)
518 } else {
519 1
520 }
521 }
522 FieldType::OptionalStructType(name) => {
523 if let Some(struct_def) = schema.find_struct(name) {
524 Struct::calculate_alignment(struct_def, schema)
525 } else {
526 1
527 }
528 }
529 FieldType::Nullable(inner) => inner.alignment(schema),
530 _ => 1,
531 }
532 }
533
534 pub fn supports_range_queries(&self) -> bool {
536 matches!(
537 self,
538 FieldType::U32
539 | FieldType::U64
540 | FieldType::I32
541 | FieldType::I64
542 | FieldType::F64
543 | FieldType::Timestamp
544 )
545 }
546
547 pub fn default_index_type(&self) -> IndexType {
549 if self.supports_range_queries() {
550 IndexType::BTree
551 } else {
552 IndexType::Hash
553 }
554 }
555}
556
557impl Struct {
558 pub fn calculate_size(struct_def: &Struct, schema: &Schema) -> usize {
560 let mut size = 0;
561 let mut max_alignment = 1;
562
563 for field in &struct_def.fields {
564 let field_size = field.field_type.size_in_bytes(schema);
565 let field_align = field.field_type.alignment(schema);
566
567 max_alignment = max_alignment.max(field_align);
568
569 if size % field_align != 0 {
571 size += field_align - (size % field_align);
572 }
573
574 size += field_size;
575 }
576
577 if size % max_alignment != 0 {
579 size += max_alignment - (size % max_alignment);
580 }
581
582 size
583 }
584
585 pub fn calculate_alignment(struct_def: &Struct, schema: &Schema) -> usize {
587 struct_def
588 .fields
589 .iter()
590 .map(|f| f.field_type.alignment(schema))
591 .max()
592 .unwrap_or(1)
593 }
594}
595
596impl Field {
597 pub fn has_constraint(&self, name: &str) -> bool {
599 self.constraints.iter().any(|c| c.name == name)
600 }
601
602 pub fn get_constraint(&self, name: &str) -> Option<&Constraint> {
604 self.constraints.iter().find(|c| c.name == name)
605 }
606
607 pub fn is_nullable(&self) -> bool {
609 matches!(
610 &self.field_type,
611 FieldType::Relation(RelationType::OptionalReference(_))
612 | FieldType::OptionalStructType(_)
613 | FieldType::Nullable(_)
614 )
615 }
616}
617
618impl RelationType {
619 pub fn target_model(&self) -> &str {
620 match self {
621 RelationType::OneToMany(model) => model,
622 RelationType::RequiredReference(model) => model,
623 RelationType::OptionalReference(model) => model,
624 RelationType::ManyToMany(model) => model,
625 }
626 }
627
628 pub fn is_one_to_many(&self) -> bool {
629 matches!(self, RelationType::OneToMany(_))
630 }
631
632 pub fn is_reference(&self) -> bool {
633 matches!(
634 self,
635 RelationType::RequiredReference(_) | RelationType::OptionalReference(_)
636 )
637 }
638
639 pub fn is_many_to_many(&self) -> bool {
640 matches!(self, RelationType::ManyToMany(_))
641 }
642}