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