1use schemars::JsonSchema;
2
3use serde::{Deserialize, Serialize};
4use std::collections::{HashMap, HashSet};
5
6use crate::schema::{
7 StrOrBoolOrArray, column::ColumnDef, constraint::TableConstraint,
8 foreign_key::ForeignKeySyntax, names::TableName, primary_key::PrimaryKeySyntax,
9};
10
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum TableValidationError {
13 DuplicateIndexColumn {
14 index_name: String,
15 column_name: String,
16 },
17 InvalidForeignKeyFormat {
18 column_name: String,
19 value: String,
20 },
21}
22
23impl std::fmt::Display for TableValidationError {
24 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25 match self {
26 TableValidationError::DuplicateIndexColumn {
27 index_name,
28 column_name,
29 } => {
30 write!(
31 f,
32 "Duplicate index '{}' on column '{}': the same index name cannot be applied to the same column multiple times",
33 index_name, column_name
34 )
35 }
36 TableValidationError::InvalidForeignKeyFormat { column_name, value } => {
37 write!(
38 f,
39 "Invalid foreign key format '{}' on column '{}': expected 'table.column' format",
40 value, column_name
41 )
42 }
43 }
44 }
45}
46
47impl std::error::Error for TableValidationError {}
48
49#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
50#[serde(rename_all = "snake_case")]
51pub struct TableDef {
52 pub name: TableName,
53 #[serde(skip_serializing_if = "Option::is_none")]
54 pub description: Option<String>,
55 pub columns: Vec<ColumnDef>,
56 #[serde(default, skip_serializing_if = "Vec::is_empty")]
57 pub constraints: Vec<TableConstraint>,
58}
59
60impl TableDef {
61 pub fn normalize(&self) -> Result<Self, TableValidationError> {
69 let mut constraints = self.constraints.clone();
70
71 let mut pk_columns: Vec<String> = Vec::new();
73 let mut pk_auto_increment = false;
74
75 for col in &self.columns {
76 if let Some(ref pk) = col.primary_key {
77 match pk {
78 PrimaryKeySyntax::Bool(true) => {
79 pk_columns.push(col.name.clone());
80 }
81 PrimaryKeySyntax::Bool(false) => {}
82 PrimaryKeySyntax::Object(pk_def) => {
83 pk_columns.push(col.name.clone());
84 if pk_def.auto_increment {
85 pk_auto_increment = true;
86 }
87 }
88 }
89 }
90 }
91
92 if !pk_columns.is_empty() {
94 let has_pk_constraint = constraints
95 .iter()
96 .any(|c| matches!(c, TableConstraint::PrimaryKey { .. }));
97
98 if !has_pk_constraint {
99 constraints.push(TableConstraint::PrimaryKey {
100 auto_increment: pk_auto_increment,
101 columns: pk_columns,
102 });
103 }
104 }
105
106 let mut unique_groups: HashMap<String, Vec<String>> = HashMap::new();
109 let mut unique_order: Vec<String> = Vec::new(); for col in &self.columns {
112 if let Some(ref unique_val) = col.unique {
113 match unique_val {
114 StrOrBoolOrArray::Str(name) => {
115 let unique_name = name.clone();
117
118 if !unique_groups.contains_key(&unique_name) {
119 unique_order.push(unique_name.clone());
120 }
121
122 unique_groups
123 .entry(unique_name)
124 .or_default()
125 .push(col.name.clone());
126 }
127 StrOrBoolOrArray::Bool(true) => {
128 let group_key = format!("__auto_{}", col.name);
130
131 if !unique_groups.contains_key(&group_key) {
132 unique_order.push(group_key.clone());
133 }
134
135 unique_groups
136 .entry(group_key)
137 .or_default()
138 .push(col.name.clone());
139 }
140 StrOrBoolOrArray::Bool(false) => continue,
141 StrOrBoolOrArray::Array(names) => {
142 for unique_name in names {
145 if !unique_groups.contains_key(unique_name.as_str()) {
146 unique_order.push(unique_name.clone());
147 }
148
149 unique_groups
150 .entry(unique_name.clone())
151 .or_default()
152 .push(col.name.clone());
153 }
154 }
155 }
156 }
157 }
158
159 for unique_name in unique_order {
161 let columns = unique_groups.get(&unique_name).unwrap().clone();
162
163 let constraint_name = if unique_name.starts_with("__auto_") {
166 None
168 } else {
169 Some(unique_name.clone())
171 };
172
173 let exists = constraints.iter().any(|c| {
175 if let TableConstraint::Unique {
176 name,
177 columns: cols,
178 } = c
179 {
180 match (&constraint_name, name) {
182 (Some(n1), Some(n2)) => n1 == n2,
183 (None, None) => cols == &columns,
184 _ => false,
185 }
186 } else {
187 false
188 }
189 });
190
191 if !exists {
192 constraints.push(TableConstraint::Unique {
193 name: constraint_name,
194 columns,
195 });
196 }
197 }
198
199 for col in &self.columns {
201 if let Some(ref fk_syntax) = col.foreign_key {
203 let (ref_table, ref_columns, on_delete, on_update) = match fk_syntax {
205 ForeignKeySyntax::String(s) => {
206 let parts: Vec<&str> = s.split('.').collect();
208 if parts.len() != 2 || parts[0].is_empty() || parts[1].is_empty() {
209 return Err(TableValidationError::InvalidForeignKeyFormat {
210 column_name: col.name.clone(),
211 value: s.clone(),
212 });
213 }
214 (parts[0].to_string(), vec![parts[1].to_string()], None, None)
215 }
216 ForeignKeySyntax::Reference(ref_syntax) => {
217 let parts: Vec<&str> = ref_syntax.references.split('.').collect();
219 if parts.len() != 2 || parts[0].is_empty() || parts[1].is_empty() {
220 return Err(TableValidationError::InvalidForeignKeyFormat {
221 column_name: col.name.clone(),
222 value: ref_syntax.references.clone(),
223 });
224 }
225 (
226 parts[0].to_string(),
227 vec![parts[1].to_string()],
228 ref_syntax.on_delete.clone(),
229 ref_syntax.on_update.clone(),
230 )
231 }
232 ForeignKeySyntax::Object(fk_def) => (
233 fk_def.ref_table.clone(),
234 fk_def.ref_columns.clone(),
235 fk_def.on_delete.clone(),
236 fk_def.on_update.clone(),
237 ),
238 };
239
240 let exists = constraints.iter().any(|c| {
242 if let TableConstraint::ForeignKey { columns, .. } = c {
243 columns.len() == 1 && columns[0] == col.name
244 } else {
245 false
246 }
247 });
248
249 if !exists {
250 constraints.push(TableConstraint::ForeignKey {
251 name: None,
252 columns: vec![col.name.clone()],
253 ref_table,
254 ref_columns,
255 on_delete,
256 on_update,
257 });
258 }
259 }
260 }
261
262 let mut index_groups: HashMap<String, Vec<String>> = HashMap::new();
265 let mut index_order: Vec<String> = Vec::new(); let mut inline_index_column_tracker: HashMap<String, HashSet<String>> = HashMap::new();
269
270 for col in &self.columns {
271 if let Some(ref index_val) = col.index {
272 match index_val {
273 StrOrBoolOrArray::Str(name) => {
274 let index_name = name.clone();
276
277 if let Some(columns) = inline_index_column_tracker.get(name.as_str())
279 && columns.contains(col.name.as_str())
280 {
281 return Err(TableValidationError::DuplicateIndexColumn {
282 index_name: name.clone(),
283 column_name: col.name.clone(),
284 });
285 }
286
287 if !index_groups.contains_key(&index_name) {
288 index_order.push(index_name.clone());
289 }
290
291 index_groups
292 .entry(index_name.clone())
293 .or_default()
294 .push(col.name.clone());
295
296 inline_index_column_tracker
297 .entry(index_name)
298 .or_default()
299 .insert(col.name.clone());
300 }
301 StrOrBoolOrArray::Bool(true) => {
302 let group_key = format!("__auto_{}", col.name);
306
307 if let Some(columns) = inline_index_column_tracker.get(group_key.as_str())
309 && columns.contains(col.name.as_str())
310 {
311 return Err(TableValidationError::DuplicateIndexColumn {
312 index_name: group_key.clone(),
313 column_name: col.name.clone(),
314 });
315 }
316
317 if !index_groups.contains_key(&group_key) {
318 index_order.push(group_key.clone());
319 }
320
321 index_groups
322 .entry(group_key.clone())
323 .or_default()
324 .push(col.name.clone());
325
326 inline_index_column_tracker
327 .entry(group_key)
328 .or_default()
329 .insert(col.name.clone());
330 }
331 StrOrBoolOrArray::Bool(false) => continue,
332 StrOrBoolOrArray::Array(names) => {
333 let mut seen_in_array = HashSet::new();
337 for index_name in names {
338 if seen_in_array.contains(index_name.as_str()) {
340 return Err(TableValidationError::DuplicateIndexColumn {
341 index_name: index_name.clone(),
342 column_name: col.name.clone(),
343 });
344 }
345 seen_in_array.insert(index_name.clone());
346
347 if let Some(columns) =
350 inline_index_column_tracker.get(index_name.as_str())
351 && columns.contains(col.name.as_str())
352 {
353 return Err(TableValidationError::DuplicateIndexColumn {
354 index_name: index_name.clone(),
355 column_name: col.name.clone(),
356 });
357 }
358
359 if !index_groups.contains_key(index_name.as_str()) {
360 index_order.push(index_name.clone());
361 }
362
363 index_groups
364 .entry(index_name.clone())
365 .or_default()
366 .push(col.name.clone());
367
368 inline_index_column_tracker
369 .entry(index_name.clone())
370 .or_default()
371 .insert(col.name.clone());
372 }
373 }
374 }
375 }
376 }
377
378 for index_name in index_order {
380 let columns = index_groups.get(&index_name).unwrap().clone();
381
382 let constraint_name = if index_name.starts_with("__auto_") {
385 None
387 } else {
388 Some(index_name.clone())
390 };
391
392 let exists = constraints.iter().any(|c| {
394 if let TableConstraint::Index {
395 name,
396 columns: cols,
397 } = c
398 {
399 match (&constraint_name, name) {
401 (Some(n1), Some(n2)) => n1 == n2,
402 (None, None) => cols == &columns,
403 _ => false,
404 }
405 } else {
406 false
407 }
408 });
409
410 if !exists {
411 constraints.push(TableConstraint::Index {
412 name: constraint_name,
413 columns,
414 });
415 }
416 }
417
418 Ok(TableDef {
419 name: self.name.clone(),
420 description: self.description.clone(),
421 columns: self.columns.clone(),
422 constraints,
423 })
424 }
425}
426
427#[cfg(test)]
428mod tests {
429 use super::*;
430 use crate::schema::column::{ColumnType, SimpleColumnType};
431 use crate::schema::foreign_key::{ForeignKeyDef, ForeignKeySyntax};
432 use crate::schema::primary_key::PrimaryKeySyntax;
433 use crate::schema::reference::ReferenceAction;
434 use crate::schema::str_or_bool::StrOrBoolOrArray;
435
436 fn col(name: &str, ty: ColumnType) -> ColumnDef {
437 ColumnDef {
438 name: name.to_string(),
439 r#type: ty,
440 nullable: true,
441 default: None,
442 comment: None,
443 primary_key: None,
444 unique: None,
445 index: None,
446 foreign_key: None,
447 }
448 }
449
450 #[test]
451 fn normalize_inline_primary_key() {
452 let mut id_col = col("id", ColumnType::Simple(SimpleColumnType::Integer));
453 id_col.primary_key = Some(PrimaryKeySyntax::Bool(true));
454
455 let table = TableDef {
456 name: "users".into(),
457 description: None,
458 columns: vec![
459 id_col,
460 col("name", ColumnType::Simple(SimpleColumnType::Text)),
461 ],
462 constraints: vec![],
463 };
464
465 let normalized = table.normalize().unwrap();
466 assert_eq!(normalized.constraints.len(), 1);
467 assert!(matches!(
468 &normalized.constraints[0],
469 TableConstraint::PrimaryKey { columns, .. } if columns == &["id".to_string()]
470 ));
471 }
472
473 #[test]
474 fn normalize_multiple_inline_primary_keys() {
475 let mut id_col = col("id", ColumnType::Simple(SimpleColumnType::Integer));
476 id_col.primary_key = Some(PrimaryKeySyntax::Bool(true));
477
478 let mut tenant_col = col("tenant_id", ColumnType::Simple(SimpleColumnType::Integer));
479 tenant_col.primary_key = Some(PrimaryKeySyntax::Bool(true));
480
481 let table = TableDef {
482 name: "users".into(),
483 description: None,
484 columns: vec![id_col, tenant_col],
485 constraints: vec![],
486 };
487
488 let normalized = table.normalize().unwrap();
489 assert_eq!(normalized.constraints.len(), 1);
490 assert!(matches!(
491 &normalized.constraints[0],
492 TableConstraint::PrimaryKey { columns, .. } if columns == &["id".to_string(), "tenant_id".to_string()]
493 ));
494 }
495
496 #[test]
497 fn normalize_does_not_duplicate_existing_pk() {
498 let mut id_col = col("id", ColumnType::Simple(SimpleColumnType::Integer));
499 id_col.primary_key = Some(PrimaryKeySyntax::Bool(true));
500
501 let table = TableDef {
502 name: "users".into(),
503 description: None,
504 columns: vec![id_col],
505 constraints: vec![TableConstraint::PrimaryKey {
506 auto_increment: false,
507 columns: vec!["id".into()],
508 }],
509 };
510
511 let normalized = table.normalize().unwrap();
512 assert_eq!(normalized.constraints.len(), 1);
513 }
514
515 #[test]
516 fn normalize_ignores_primary_key_false() {
517 let mut id_col = col("id", ColumnType::Simple(SimpleColumnType::Integer));
518 id_col.primary_key = Some(PrimaryKeySyntax::Bool(false));
519
520 let table = TableDef {
521 name: "users".into(),
522 description: None,
523 columns: vec![
524 id_col,
525 col("name", ColumnType::Simple(SimpleColumnType::Text)),
526 ],
527 constraints: vec![],
528 };
529
530 let normalized = table.normalize().unwrap();
531 assert_eq!(normalized.constraints.len(), 0);
533 }
534
535 #[test]
536 fn normalize_inline_unique_bool() {
537 let mut email_col = col("email", ColumnType::Simple(SimpleColumnType::Text));
538 email_col.unique = Some(StrOrBoolOrArray::Bool(true));
539
540 let table = TableDef {
541 name: "users".into(),
542 description: None,
543 columns: vec![
544 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
545 email_col,
546 ],
547 constraints: vec![],
548 };
549
550 let normalized = table.normalize().unwrap();
551 assert_eq!(normalized.constraints.len(), 1);
552 assert!(matches!(
553 &normalized.constraints[0],
554 TableConstraint::Unique { name: None, columns } if columns == &["email".to_string()]
555 ));
556 }
557
558 #[test]
559 fn normalize_inline_unique_with_name() {
560 let mut email_col = col("email", ColumnType::Simple(SimpleColumnType::Text));
561 email_col.unique = Some(StrOrBoolOrArray::Str("uq_users_email".into()));
562
563 let table = TableDef {
564 name: "users".into(),
565 description: None,
566 columns: vec![
567 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
568 email_col,
569 ],
570 constraints: vec![],
571 };
572
573 let normalized = table.normalize().unwrap();
574 assert_eq!(normalized.constraints.len(), 1);
575 assert!(matches!(
576 &normalized.constraints[0],
577 TableConstraint::Unique { name: Some(n), columns }
578 if n == "uq_users_email" && columns == &["email".to_string()]
579 ));
580 }
581
582 #[test]
583 fn normalize_composite_unique_from_string_name() {
584 let mut route_col = col("join_route", ColumnType::Simple(SimpleColumnType::Text));
587 route_col.unique = Some(StrOrBoolOrArray::Str("route_provider_id".into()));
588
589 let mut provider_col = col("provider_id", ColumnType::Simple(SimpleColumnType::Text));
590 provider_col.unique = Some(StrOrBoolOrArray::Str("route_provider_id".into()));
591
592 let table = TableDef {
593 name: "user".into(),
594 description: None,
595 columns: vec![
596 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
597 route_col,
598 provider_col,
599 ],
600 constraints: vec![],
601 };
602
603 let normalized = table.normalize().unwrap();
604 assert_eq!(normalized.constraints.len(), 1);
605 assert!(matches!(
606 &normalized.constraints[0],
607 TableConstraint::Unique { name: Some(n), columns }
608 if n == "route_provider_id"
609 && columns == &["join_route".to_string(), "provider_id".to_string()]
610 ));
611 }
612
613 #[test]
614 fn normalize_unique_name_mismatch_creates_both_constraints() {
615 let mut email_col = col("email", ColumnType::Simple(SimpleColumnType::Text));
618 email_col.unique = Some(StrOrBoolOrArray::Str("named_unique".into()));
619
620 let table = TableDef {
621 name: "user".into(),
622 description: None,
623 columns: vec![
624 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
625 email_col,
626 ],
627 constraints: vec![
628 TableConstraint::Unique {
630 name: None,
631 columns: vec!["email".into()],
632 },
633 ],
634 };
635
636 let normalized = table.normalize().unwrap();
637
638 let unique_constraints: Vec<_> = normalized
640 .constraints
641 .iter()
642 .filter(|c| matches!(c, TableConstraint::Unique { .. }))
643 .collect();
644 assert_eq!(
645 unique_constraints.len(),
646 2,
647 "Should keep both named and unnamed unique constraints as they don't match"
648 );
649
650 let has_named = unique_constraints.iter().any(
652 |c| matches!(c, TableConstraint::Unique { name: Some(n), .. } if n == "named_unique"),
653 );
654 let has_unnamed = unique_constraints
655 .iter()
656 .any(|c| matches!(c, TableConstraint::Unique { name: None, .. }));
657
658 assert!(has_named, "Should have named unique constraint");
659 assert!(has_unnamed, "Should have unnamed unique constraint");
660 }
661
662 #[test]
663 fn normalize_inline_index_bool() {
664 let mut name_col = col("name", ColumnType::Simple(SimpleColumnType::Text));
665 name_col.index = Some(StrOrBoolOrArray::Bool(true));
666
667 let table = TableDef {
668 name: "users".into(),
669 description: None,
670 columns: vec![
671 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
672 name_col,
673 ],
674 constraints: vec![],
675 };
676
677 let normalized = table.normalize().unwrap();
678 let indexes: Vec<_> = normalized
680 .constraints
681 .iter()
682 .filter(|c| matches!(c, TableConstraint::Index { .. }))
683 .collect();
684 assert_eq!(indexes.len(), 1);
685 assert!(matches!(
688 indexes[0],
689 TableConstraint::Index { name: None, columns }
690 if columns == &["name".to_string()]
691 ));
692 }
693
694 #[test]
695 fn normalize_inline_index_with_name() {
696 let mut name_col = col("name", ColumnType::Simple(SimpleColumnType::Text));
697 name_col.index = Some(StrOrBoolOrArray::Str("custom_idx_name".into()));
698
699 let table = TableDef {
700 name: "users".into(),
701 description: None,
702 columns: vec![
703 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
704 name_col,
705 ],
706 constraints: vec![],
707 };
708
709 let normalized = table.normalize().unwrap();
710 let indexes: Vec<_> = normalized
711 .constraints
712 .iter()
713 .filter(|c| matches!(c, TableConstraint::Index { .. }))
714 .collect();
715 assert_eq!(indexes.len(), 1);
716 assert!(matches!(
717 indexes[0],
718 TableConstraint::Index { name: Some(n), .. }
719 if n == "custom_idx_name"
720 ));
721 }
722
723 #[test]
724 fn normalize_inline_foreign_key() {
725 let mut user_id_col = col("user_id", ColumnType::Simple(SimpleColumnType::Integer));
726 user_id_col.foreign_key = Some(ForeignKeySyntax::Object(ForeignKeyDef {
727 ref_table: "users".into(),
728 ref_columns: vec!["id".into()],
729 on_delete: Some(ReferenceAction::Cascade),
730 on_update: None,
731 }));
732
733 let table = TableDef {
734 name: "posts".into(),
735 description: None,
736 columns: vec![
737 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
738 user_id_col,
739 ],
740 constraints: vec![],
741 };
742
743 let normalized = table.normalize().unwrap();
744 assert_eq!(normalized.constraints.len(), 1);
745 assert!(matches!(
746 &normalized.constraints[0],
747 TableConstraint::ForeignKey {
748 name: None,
749 columns,
750 ref_table,
751 ref_columns,
752 on_delete: Some(ReferenceAction::Cascade),
753 on_update: None,
754 } if columns == &["user_id".to_string()]
755 && ref_table == "users"
756 && ref_columns == &["id".to_string()]
757 ));
758 }
759
760 #[test]
761 fn normalize_all_inline_constraints() {
762 let mut id_col = col("id", ColumnType::Simple(SimpleColumnType::Integer));
763 id_col.primary_key = Some(PrimaryKeySyntax::Bool(true));
764
765 let mut email_col = col("email", ColumnType::Simple(SimpleColumnType::Text));
766 email_col.unique = Some(StrOrBoolOrArray::Bool(true));
767
768 let mut name_col = col("name", ColumnType::Simple(SimpleColumnType::Text));
769 name_col.index = Some(StrOrBoolOrArray::Bool(true));
770
771 let mut user_id_col = col("org_id", ColumnType::Simple(SimpleColumnType::Integer));
772 user_id_col.foreign_key = Some(ForeignKeySyntax::Object(ForeignKeyDef {
773 ref_table: "orgs".into(),
774 ref_columns: vec!["id".into()],
775 on_delete: None,
776 on_update: None,
777 }));
778
779 let table = TableDef {
780 name: "users".into(),
781 description: None,
782 columns: vec![id_col, email_col, name_col, user_id_col],
783 constraints: vec![],
784 };
785
786 let normalized = table.normalize().unwrap();
787 let non_index_constraints: Vec<_> = normalized
790 .constraints
791 .iter()
792 .filter(|c| !matches!(c, TableConstraint::Index { .. }))
793 .collect();
794 assert_eq!(non_index_constraints.len(), 3);
795 let indexes: Vec<_> = normalized
797 .constraints
798 .iter()
799 .filter(|c| matches!(c, TableConstraint::Index { .. }))
800 .collect();
801 assert_eq!(indexes.len(), 1);
802 }
803
804 #[test]
805 fn normalize_composite_index_from_string_name() {
806 let mut updated_at_col = col(
807 "updated_at",
808 ColumnType::Simple(SimpleColumnType::Timestamp),
809 );
810 updated_at_col.index = Some(StrOrBoolOrArray::Str("tuple".into()));
811
812 let mut user_id_col = col("user_id", ColumnType::Simple(SimpleColumnType::Integer));
813 user_id_col.index = Some(StrOrBoolOrArray::Str("tuple".into()));
814
815 let table = TableDef {
816 name: "post".into(),
817 description: None,
818 columns: vec![
819 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
820 updated_at_col,
821 user_id_col,
822 ],
823 constraints: vec![],
824 };
825
826 let normalized = table.normalize().unwrap();
827 let indexes: Vec<_> = normalized
828 .constraints
829 .iter()
830 .filter_map(|c| {
831 if let TableConstraint::Index { name, columns } = c {
832 Some((name.clone(), columns.clone()))
833 } else {
834 None
835 }
836 })
837 .collect();
838 assert_eq!(indexes.len(), 1);
839 assert_eq!(indexes[0].0, Some("tuple".to_string()));
840 assert_eq!(
841 indexes[0].1,
842 vec!["updated_at".to_string(), "user_id".to_string()]
843 );
844 }
845
846 #[test]
847 fn normalize_multiple_different_indexes() {
848 let mut col1 = col("col1", ColumnType::Simple(SimpleColumnType::Text));
849 col1.index = Some(StrOrBoolOrArray::Str("idx_a".into()));
850
851 let mut col2 = col("col2", ColumnType::Simple(SimpleColumnType::Text));
852 col2.index = Some(StrOrBoolOrArray::Str("idx_a".into()));
853
854 let mut col3 = col("col3", ColumnType::Simple(SimpleColumnType::Text));
855 col3.index = Some(StrOrBoolOrArray::Str("idx_b".into()));
856
857 let mut col4 = col("col4", ColumnType::Simple(SimpleColumnType::Text));
858 col4.index = Some(StrOrBoolOrArray::Bool(true));
859
860 let table = TableDef {
861 name: "test".into(),
862 description: None,
863 columns: vec![
864 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
865 col1,
866 col2,
867 col3,
868 col4,
869 ],
870 constraints: vec![],
871 };
872
873 let normalized = table.normalize().unwrap();
874 let indexes: Vec<_> = normalized
875 .constraints
876 .iter()
877 .filter_map(|c| {
878 if let TableConstraint::Index { name, columns } = c {
879 Some((name.clone(), columns.clone()))
880 } else {
881 None
882 }
883 })
884 .collect();
885 assert_eq!(indexes.len(), 3);
886
887 let idx_a = indexes
889 .iter()
890 .find(|(n, _)| n == &Some("idx_a".to_string()))
891 .unwrap();
892 assert_eq!(idx_a.1, vec!["col1".to_string(), "col2".to_string()]);
893
894 let idx_b = indexes
896 .iter()
897 .find(|(n, _)| n == &Some("idx_b".to_string()))
898 .unwrap();
899 assert_eq!(idx_b.1, vec!["col3".to_string()]);
900
901 let idx_col4 = indexes.iter().find(|(n, _)| n.is_none()).unwrap();
903 assert_eq!(idx_col4.1, vec!["col4".to_string()]);
904 }
905
906 #[test]
907 fn normalize_false_values_are_ignored() {
908 let mut email_col = col("email", ColumnType::Simple(SimpleColumnType::Text));
909 email_col.unique = Some(StrOrBoolOrArray::Bool(false));
910 email_col.index = Some(StrOrBoolOrArray::Bool(false));
911
912 let table = TableDef {
913 name: "users".into(),
914 description: None,
915 columns: vec![
916 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
917 email_col,
918 ],
919 constraints: vec![],
920 };
921
922 let normalized = table.normalize().unwrap();
923 assert_eq!(normalized.constraints.len(), 0);
924 }
925
926 #[test]
927 fn normalize_table_without_primary_key() {
928 let table = TableDef {
931 name: "users".into(),
932 description: None,
933 columns: vec![
934 col("name", ColumnType::Simple(SimpleColumnType::Text)),
935 col("email", ColumnType::Simple(SimpleColumnType::Text)),
936 ],
937 constraints: vec![],
938 };
939
940 let normalized = table.normalize().unwrap();
941 assert_eq!(normalized.constraints.len(), 0);
943 }
944
945 #[test]
946 fn normalize_multiple_indexes_from_same_array() {
947 let mut updated_at_col = col(
949 "updated_at",
950 ColumnType::Simple(SimpleColumnType::Timestamp),
951 );
952 updated_at_col.index = Some(StrOrBoolOrArray::Array(vec![
953 "tuple".into(),
954 "tuple2".into(),
955 ]));
956
957 let mut user_id_col = col("user_id", ColumnType::Simple(SimpleColumnType::Integer));
958 user_id_col.index = Some(StrOrBoolOrArray::Array(vec![
959 "tuple".into(),
960 "tuple2".into(),
961 ]));
962
963 let table = TableDef {
964 name: "post".into(),
965 description: None,
966 columns: vec![
967 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
968 updated_at_col,
969 user_id_col,
970 ],
971 constraints: vec![],
972 };
973
974 let normalized = table.normalize().unwrap();
975 let indexes: Vec<_> = normalized
977 .constraints
978 .iter()
979 .filter_map(|c| {
980 if let TableConstraint::Index { name, columns } = c {
981 Some((name.clone(), columns.clone()))
982 } else {
983 None
984 }
985 })
986 .collect();
987 assert_eq!(indexes.len(), 2);
988
989 let tuple_idx = indexes
990 .iter()
991 .find(|(n, _)| n == &Some("tuple".to_string()))
992 .unwrap();
993 let mut sorted_cols = tuple_idx.1.clone();
994 sorted_cols.sort();
995 assert_eq!(
996 sorted_cols,
997 vec!["updated_at".to_string(), "user_id".to_string()]
998 );
999
1000 let tuple2_idx = indexes
1001 .iter()
1002 .find(|(n, _)| n == &Some("tuple2".to_string()))
1003 .unwrap();
1004 let mut sorted_cols2 = tuple2_idx.1.clone();
1005 sorted_cols2.sort();
1006 assert_eq!(
1007 sorted_cols2,
1008 vec!["updated_at".to_string(), "user_id".to_string()]
1009 );
1010 }
1011
1012 #[test]
1013 fn normalize_inline_unique_with_array_existing_constraint() {
1014 let mut col1 = col("col1", ColumnType::Simple(SimpleColumnType::Text));
1016 col1.unique = Some(StrOrBoolOrArray::Array(vec!["uq_group".into()]));
1017
1018 let mut col2 = col("col2", ColumnType::Simple(SimpleColumnType::Text));
1019 col2.unique = Some(StrOrBoolOrArray::Array(vec!["uq_group".into()]));
1020
1021 let table = TableDef {
1022 name: "test".into(),
1023 description: None,
1024 columns: vec![
1025 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
1026 col1,
1027 col2,
1028 ],
1029 constraints: vec![],
1030 };
1031
1032 let normalized = table.normalize().unwrap();
1033 assert_eq!(normalized.constraints.len(), 1);
1034 let unique_constraint = &normalized.constraints[0];
1035 assert!(matches!(
1036 unique_constraint,
1037 TableConstraint::Unique { name: Some(n), columns: _ }
1038 if n == "uq_group"
1039 ));
1040 if let TableConstraint::Unique { columns, .. } = unique_constraint {
1041 let mut sorted_cols = columns.clone();
1042 sorted_cols.sort();
1043 assert_eq!(sorted_cols, vec!["col1".to_string(), "col2".to_string()]);
1044 }
1045 }
1046
1047 #[test]
1048 fn normalize_inline_unique_with_array_column_already_in_constraint() {
1049 let mut col1 = col("col1", ColumnType::Simple(SimpleColumnType::Text));
1051 col1.unique = Some(StrOrBoolOrArray::Array(vec!["uq_group".into()]));
1052
1053 let table = TableDef {
1054 name: "test".into(),
1055 description: None,
1056 columns: vec![
1057 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
1058 col1.clone(),
1059 ],
1060 constraints: vec![],
1061 };
1062
1063 let normalized1 = table.normalize().unwrap();
1064 assert_eq!(normalized1.constraints.len(), 1);
1065
1066 let table2 = TableDef {
1068 name: "test".into(),
1069 description: None,
1070 columns: vec![
1071 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
1072 col1,
1073 ],
1074 constraints: normalized1.constraints.clone(),
1075 };
1076
1077 let normalized2 = table2.normalize().unwrap();
1078 assert_eq!(normalized2.constraints.len(), 1);
1079 if let TableConstraint::Unique { columns, .. } = &normalized2.constraints[0] {
1080 assert_eq!(columns.len(), 1);
1081 assert_eq!(columns[0], "col1");
1082 }
1083 }
1084
1085 #[test]
1086 fn normalize_inline_unique_str_already_exists() {
1087 let mut email_col = col("email", ColumnType::Simple(SimpleColumnType::Text));
1089 email_col.unique = Some(StrOrBoolOrArray::Str("uq_email".into()));
1090
1091 let table = TableDef {
1092 name: "users".into(),
1093 description: None,
1094 columns: vec![
1095 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
1096 email_col,
1097 ],
1098 constraints: vec![TableConstraint::Unique {
1099 name: Some("uq_email".into()),
1100 columns: vec!["email".into()],
1101 }],
1102 };
1103
1104 let normalized = table.normalize().unwrap();
1105 let unique_constraints: Vec<_> = normalized
1107 .constraints
1108 .iter()
1109 .filter(|c| matches!(c, TableConstraint::Unique { .. }))
1110 .collect();
1111 assert_eq!(unique_constraints.len(), 1);
1112 }
1113
1114 #[test]
1115 fn normalize_inline_unique_bool_already_exists() {
1116 let mut email_col = col("email", ColumnType::Simple(SimpleColumnType::Text));
1118 email_col.unique = Some(StrOrBoolOrArray::Bool(true));
1119
1120 let table = TableDef {
1121 name: "users".into(),
1122 description: None,
1123 columns: vec![
1124 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
1125 email_col,
1126 ],
1127 constraints: vec![TableConstraint::Unique {
1128 name: None,
1129 columns: vec!["email".into()],
1130 }],
1131 };
1132
1133 let normalized = table.normalize().unwrap();
1134 let unique_constraints: Vec<_> = normalized
1136 .constraints
1137 .iter()
1138 .filter(|c| matches!(c, TableConstraint::Unique { .. }))
1139 .collect();
1140 assert_eq!(unique_constraints.len(), 1);
1141 }
1142
1143 #[test]
1144 fn normalize_inline_foreign_key_already_exists() {
1145 let mut user_id_col = col("user_id", ColumnType::Simple(SimpleColumnType::Integer));
1147 user_id_col.foreign_key = Some(ForeignKeySyntax::Object(ForeignKeyDef {
1148 ref_table: "users".into(),
1149 ref_columns: vec!["id".into()],
1150 on_delete: None,
1151 on_update: None,
1152 }));
1153
1154 let table = TableDef {
1155 name: "posts".into(),
1156 description: None,
1157 columns: vec![
1158 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
1159 user_id_col,
1160 ],
1161 constraints: vec![TableConstraint::ForeignKey {
1162 name: None,
1163 columns: vec!["user_id".into()],
1164 ref_table: "users".into(),
1165 ref_columns: vec!["id".into()],
1166 on_delete: None,
1167 on_update: None,
1168 }],
1169 };
1170
1171 let normalized = table.normalize().unwrap();
1172 let fk_constraints: Vec<_> = normalized
1174 .constraints
1175 .iter()
1176 .filter(|c| matches!(c, TableConstraint::ForeignKey { .. }))
1177 .collect();
1178 assert_eq!(fk_constraints.len(), 1);
1179 }
1180
1181 #[test]
1182 fn normalize_duplicate_index_same_column_str() {
1183 let mut col1 = col("col1", ColumnType::Simple(SimpleColumnType::Text));
1186 col1.index = Some(StrOrBoolOrArray::Str("idx1".into()));
1187
1188 let table = TableDef {
1189 name: "test".into(),
1190 description: None,
1191 columns: vec![
1192 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
1193 col1.clone(),
1194 {
1195 let mut c = col1.clone();
1197 c.index = Some(StrOrBoolOrArray::Str("idx1".into()));
1198 c
1199 },
1200 ],
1201 constraints: vec![],
1202 };
1203
1204 let result = table.normalize();
1205 assert!(result.is_err());
1206 if let Err(TableValidationError::DuplicateIndexColumn {
1207 index_name,
1208 column_name,
1209 }) = result
1210 {
1211 assert_eq!(index_name, "idx1");
1212 assert_eq!(column_name, "col1");
1213 } else {
1214 panic!("Expected DuplicateIndexColumn error");
1215 }
1216 }
1217
1218 #[test]
1219 fn normalize_duplicate_index_same_column_array() {
1220 let mut col1 = col("col1", ColumnType::Simple(SimpleColumnType::Text));
1222 col1.index = Some(StrOrBoolOrArray::Array(vec!["idx1".into(), "idx1".into()]));
1223
1224 let table = TableDef {
1225 name: "test".into(),
1226 description: None,
1227 columns: vec![
1228 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
1229 col1,
1230 ],
1231 constraints: vec![],
1232 };
1233
1234 let result = table.normalize();
1235 assert!(result.is_err());
1236 if let Err(TableValidationError::DuplicateIndexColumn {
1237 index_name,
1238 column_name,
1239 }) = result
1240 {
1241 assert_eq!(index_name, "idx1");
1242 assert_eq!(column_name, "col1");
1243 } else {
1244 panic!("Expected DuplicateIndexColumn error");
1245 }
1246 }
1247
1248 #[test]
1249 fn normalize_duplicate_index_same_column_multiple_definitions() {
1250 let mut col1 = col("col1", ColumnType::Simple(SimpleColumnType::Text));
1252 col1.index = Some(StrOrBoolOrArray::Str("idx1".into()));
1253
1254 let table = TableDef {
1255 name: "test".into(),
1256 description: None,
1257 columns: vec![
1258 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
1259 col1.clone(),
1260 {
1261 let mut c = col1.clone();
1262 c.index = Some(StrOrBoolOrArray::Array(vec!["idx1".into()]));
1263 c
1264 },
1265 ],
1266 constraints: vec![],
1267 };
1268
1269 let result = table.normalize();
1270 assert!(result.is_err());
1271 if let Err(TableValidationError::DuplicateIndexColumn {
1272 index_name,
1273 column_name,
1274 }) = result
1275 {
1276 assert_eq!(index_name, "idx1");
1277 assert_eq!(column_name, "col1");
1278 } else {
1279 panic!("Expected DuplicateIndexColumn error");
1280 }
1281 }
1282
1283 #[test]
1284 fn test_table_validation_error_display() {
1285 let error = TableValidationError::DuplicateIndexColumn {
1286 index_name: "idx_test".into(),
1287 column_name: "col1".into(),
1288 };
1289 let error_msg = format!("{}", error);
1290 assert!(error_msg.contains("idx_test"));
1291 assert!(error_msg.contains("col1"));
1292 assert!(error_msg.contains("Duplicate index"));
1293 }
1294
1295 #[test]
1296 fn normalize_inline_unique_str_with_different_constraint_type() {
1297 let mut email_col = col("email", ColumnType::Simple(SimpleColumnType::Text));
1299 email_col.unique = Some(StrOrBoolOrArray::Str("uq_email".into()));
1300
1301 let table = TableDef {
1302 name: "users".into(),
1303 description: None,
1304 columns: vec![
1305 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
1306 email_col,
1307 ],
1308 constraints: vec![
1309 TableConstraint::PrimaryKey {
1311 auto_increment: false,
1312 columns: vec!["id".into()],
1313 },
1314 ],
1315 };
1316
1317 let normalized = table.normalize().unwrap();
1318 assert_eq!(normalized.constraints.len(), 2);
1320 }
1321
1322 #[test]
1323 fn normalize_inline_unique_array_with_different_constraint_type() {
1324 let mut col1 = col("col1", ColumnType::Simple(SimpleColumnType::Text));
1326 col1.unique = Some(StrOrBoolOrArray::Array(vec!["uq_group".into()]));
1327
1328 let table = TableDef {
1329 name: "test".into(),
1330 description: None,
1331 columns: vec![
1332 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
1333 col1,
1334 ],
1335 constraints: vec![
1336 TableConstraint::PrimaryKey {
1338 auto_increment: false,
1339 columns: vec!["id".into()],
1340 },
1341 ],
1342 };
1343
1344 let normalized = table.normalize().unwrap();
1345 assert_eq!(normalized.constraints.len(), 2);
1347 }
1348
1349 #[test]
1350 fn normalize_duplicate_index_bool_true_same_column() {
1351 let mut col1 = col("col1", ColumnType::Simple(SimpleColumnType::Text));
1353 col1.index = Some(StrOrBoolOrArray::Bool(true));
1354
1355 let table = TableDef {
1356 name: "test".into(),
1357 description: None,
1358 columns: vec![
1359 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
1360 col1.clone(),
1361 {
1362 let mut c = col1.clone();
1364 c.index = Some(StrOrBoolOrArray::Bool(true));
1365 c
1366 },
1367 ],
1368 constraints: vec![],
1369 };
1370
1371 let result = table.normalize();
1372 assert!(result.is_err());
1373 if let Err(TableValidationError::DuplicateIndexColumn {
1374 index_name,
1375 column_name,
1376 }) = result
1377 {
1378 assert!(index_name.contains("__auto_"));
1380 assert!(index_name.contains("col1"));
1381 assert_eq!(column_name, "col1");
1382 } else {
1383 panic!("Expected DuplicateIndexColumn error");
1384 }
1385 }
1386
1387 #[test]
1388 fn normalize_inline_foreign_key_string_syntax() {
1389 let mut user_id_col = col("user_id", ColumnType::Simple(SimpleColumnType::Integer));
1391 user_id_col.foreign_key = Some(ForeignKeySyntax::String("users.id".into()));
1392
1393 let table = TableDef {
1394 name: "posts".into(),
1395 description: None,
1396 columns: vec![
1397 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
1398 user_id_col,
1399 ],
1400 constraints: vec![],
1401 };
1402
1403 let normalized = table.normalize().unwrap();
1404 assert_eq!(normalized.constraints.len(), 1);
1405 assert!(matches!(
1406 &normalized.constraints[0],
1407 TableConstraint::ForeignKey {
1408 name: None,
1409 columns,
1410 ref_table,
1411 ref_columns,
1412 on_delete: None,
1413 on_update: None,
1414 } if columns == &["user_id".to_string()]
1415 && ref_table == "users"
1416 && ref_columns == &["id".to_string()]
1417 ));
1418 }
1419
1420 #[test]
1421 fn normalize_inline_foreign_key_invalid_format_no_dot() {
1422 let mut user_id_col = col("user_id", ColumnType::Simple(SimpleColumnType::Integer));
1424 user_id_col.foreign_key = Some(ForeignKeySyntax::String("usersid".into()));
1425
1426 let table = TableDef {
1427 name: "posts".into(),
1428 description: None,
1429 columns: vec![
1430 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
1431 user_id_col,
1432 ],
1433 constraints: vec![],
1434 };
1435
1436 let result = table.normalize();
1437 assert!(result.is_err());
1438 if let Err(TableValidationError::InvalidForeignKeyFormat { column_name, value }) = result {
1439 assert_eq!(column_name, "user_id");
1440 assert_eq!(value, "usersid");
1441 } else {
1442 panic!("Expected InvalidForeignKeyFormat error");
1443 }
1444 }
1445
1446 #[test]
1447 fn normalize_inline_foreign_key_invalid_format_empty_table() {
1448 let mut user_id_col = col("user_id", ColumnType::Simple(SimpleColumnType::Integer));
1450 user_id_col.foreign_key = Some(ForeignKeySyntax::String(".id".into()));
1451
1452 let table = TableDef {
1453 name: "posts".into(),
1454 description: None,
1455 columns: vec![
1456 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
1457 user_id_col,
1458 ],
1459 constraints: vec![],
1460 };
1461
1462 let result = table.normalize();
1463 assert!(result.is_err());
1464 if let Err(TableValidationError::InvalidForeignKeyFormat { column_name, value }) = result {
1465 assert_eq!(column_name, "user_id");
1466 assert_eq!(value, ".id");
1467 } else {
1468 panic!("Expected InvalidForeignKeyFormat error");
1469 }
1470 }
1471
1472 #[test]
1473 fn normalize_inline_foreign_key_invalid_format_empty_column() {
1474 let mut user_id_col = col("user_id", ColumnType::Simple(SimpleColumnType::Integer));
1476 user_id_col.foreign_key = Some(ForeignKeySyntax::String("users.".into()));
1477
1478 let table = TableDef {
1479 name: "posts".into(),
1480 description: None,
1481 columns: vec![
1482 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
1483 user_id_col,
1484 ],
1485 constraints: vec![],
1486 };
1487
1488 let result = table.normalize();
1489 assert!(result.is_err());
1490 if let Err(TableValidationError::InvalidForeignKeyFormat { column_name, value }) = result {
1491 assert_eq!(column_name, "user_id");
1492 assert_eq!(value, "users.");
1493 } else {
1494 panic!("Expected InvalidForeignKeyFormat error");
1495 }
1496 }
1497
1498 #[test]
1499 fn normalize_inline_foreign_key_invalid_format_too_many_parts() {
1500 let mut user_id_col = col("user_id", ColumnType::Simple(SimpleColumnType::Integer));
1502 user_id_col.foreign_key = Some(ForeignKeySyntax::String("schema.users.id".into()));
1503
1504 let table = TableDef {
1505 name: "posts".into(),
1506 description: None,
1507 columns: vec![
1508 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
1509 user_id_col,
1510 ],
1511 constraints: vec![],
1512 };
1513
1514 let result = table.normalize();
1515 assert!(result.is_err());
1516 if let Err(TableValidationError::InvalidForeignKeyFormat { column_name, value }) = result {
1517 assert_eq!(column_name, "user_id");
1518 assert_eq!(value, "schema.users.id");
1519 } else {
1520 panic!("Expected InvalidForeignKeyFormat error");
1521 }
1522 }
1523
1524 #[test]
1525 fn normalize_inline_primary_key_with_auto_increment() {
1526 use crate::schema::primary_key::PrimaryKeyDef;
1527
1528 let mut id_col = col("id", ColumnType::Simple(SimpleColumnType::Integer));
1529 id_col.primary_key = Some(PrimaryKeySyntax::Object(PrimaryKeyDef {
1530 auto_increment: true,
1531 }));
1532
1533 let table = TableDef {
1534 name: "users".into(),
1535 description: None,
1536 columns: vec![
1537 id_col,
1538 col("name", ColumnType::Simple(SimpleColumnType::Text)),
1539 ],
1540 constraints: vec![],
1541 };
1542
1543 let normalized = table.normalize().unwrap();
1544 assert_eq!(normalized.constraints.len(), 1);
1545 assert!(matches!(
1546 &normalized.constraints[0],
1547 TableConstraint::PrimaryKey { auto_increment: true, columns } if columns == &["id".to_string()]
1548 ));
1549 }
1550
1551 #[test]
1552 fn normalize_duplicate_inline_index_on_same_column() {
1553 use crate::schema::str_or_bool::StrOrBoolOrArray;
1556
1557 let mut email_col = col("email", ColumnType::Simple(SimpleColumnType::Text));
1559 email_col.index = Some(StrOrBoolOrArray::Array(vec![
1560 "idx_email".into(),
1561 "idx_email".into(), ]));
1563
1564 let table = TableDef {
1565 name: "users".into(),
1566 description: None,
1567 columns: vec![
1568 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
1569 email_col,
1570 ],
1571 constraints: vec![],
1572 };
1573
1574 let result = table.normalize();
1575 assert!(result.is_err());
1576 if let Err(TableValidationError::DuplicateIndexColumn {
1577 index_name,
1578 column_name,
1579 }) = result
1580 {
1581 assert_eq!(index_name, "idx_email");
1582 assert_eq!(column_name, "email");
1583 } else {
1584 panic!("Expected DuplicateIndexColumn error, got: {:?}", result);
1585 }
1586 }
1587
1588 #[test]
1589 fn test_invalid_foreign_key_format_error_display() {
1590 let error = TableValidationError::InvalidForeignKeyFormat {
1591 column_name: "user_id".into(),
1592 value: "invalid".into(),
1593 };
1594 let error_msg = format!("{}", error);
1595 assert!(error_msg.contains("user_id"));
1596 assert!(error_msg.contains("invalid"));
1597 assert!(error_msg.contains("table.column"));
1598 }
1599
1600 #[test]
1601 fn normalize_inline_foreign_key_reference_syntax() {
1602 use crate::schema::foreign_key::ReferenceSyntaxDef;
1604
1605 let mut user_id_col = col("user_id", ColumnType::Simple(SimpleColumnType::Integer));
1606 user_id_col.foreign_key = Some(ForeignKeySyntax::Reference(ReferenceSyntaxDef {
1607 references: "users.id".into(),
1608 on_delete: Some(ReferenceAction::Cascade),
1609 on_update: None,
1610 }));
1611
1612 let table = TableDef {
1613 name: "posts".into(),
1614 description: None,
1615 columns: vec![
1616 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
1617 user_id_col,
1618 ],
1619 constraints: vec![],
1620 };
1621
1622 let normalized = table.normalize().unwrap();
1623 assert_eq!(normalized.constraints.len(), 1);
1624 assert!(matches!(
1625 &normalized.constraints[0],
1626 TableConstraint::ForeignKey {
1627 name: None,
1628 columns,
1629 ref_table,
1630 ref_columns,
1631 on_delete: Some(ReferenceAction::Cascade),
1632 on_update: None,
1633 } if columns == &["user_id".to_string()]
1634 && ref_table == "users"
1635 && ref_columns == &["id".to_string()]
1636 ));
1637 }
1638
1639 #[test]
1640 fn normalize_inline_foreign_key_reference_syntax_invalid_format() {
1641 use crate::schema::foreign_key::ReferenceSyntaxDef;
1643
1644 let mut user_id_col = col("user_id", ColumnType::Simple(SimpleColumnType::Integer));
1645 user_id_col.foreign_key = Some(ForeignKeySyntax::Reference(ReferenceSyntaxDef {
1646 references: "invalid_no_dot".into(),
1647 on_delete: None,
1648 on_update: None,
1649 }));
1650
1651 let table = TableDef {
1652 name: "posts".into(),
1653 description: None,
1654 columns: vec![
1655 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
1656 user_id_col,
1657 ],
1658 constraints: vec![],
1659 };
1660
1661 let result = table.normalize();
1662 assert!(result.is_err());
1663 if let Err(TableValidationError::InvalidForeignKeyFormat { column_name, value }) = result {
1664 assert_eq!(column_name, "user_id");
1665 assert_eq!(value, "invalid_no_dot");
1666 } else {
1667 panic!("Expected InvalidForeignKeyFormat error");
1668 }
1669 }
1670
1671 #[test]
1672 fn deserialize_table_without_constraints() {
1673 let json = r#"{
1675 "name": "users",
1676 "columns": [
1677 { "name": "id", "type": "integer", "nullable": false }
1678 ]
1679 }"#;
1680
1681 let table: TableDef = serde_json::from_str(json).unwrap();
1682 assert_eq!(table.name.as_str(), "users");
1683 assert!(table.constraints.is_empty());
1684 }
1685
1686 #[test]
1687 fn deserialize_foreign_key_reference_syntax() {
1688 let json = r#"{
1690 "name": "posts",
1691 "columns": [
1692 { "name": "id", "type": "integer", "nullable": false },
1693 {
1694 "name": "user_id",
1695 "type": "integer",
1696 "nullable": false,
1697 "foreign_key": { "references": "users.id", "on_delete": "cascade" }
1698 }
1699 ]
1700 }"#;
1701
1702 let table: TableDef = serde_json::from_str(json).unwrap();
1703 assert_eq!(table.columns.len(), 2);
1704
1705 let user_id_col = &table.columns[1];
1706 assert!(user_id_col.foreign_key.is_some());
1707
1708 if let Some(ForeignKeySyntax::Reference(ref_syntax)) = &user_id_col.foreign_key {
1709 assert_eq!(ref_syntax.references, "users.id");
1710 assert_eq!(ref_syntax.on_delete, Some(ReferenceAction::Cascade));
1711 } else {
1712 panic!("Expected Reference syntax");
1713 }
1714 }
1715}