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 pub columns: Vec<ColumnDef>,
54 pub constraints: Vec<TableConstraint>,
55}
56
57impl TableDef {
58 pub fn normalize(&self) -> Result<Self, TableValidationError> {
66 let mut constraints = self.constraints.clone();
67
68 let mut pk_columns: Vec<String> = Vec::new();
70 let mut pk_auto_increment = false;
71
72 for col in &self.columns {
73 if let Some(ref pk) = col.primary_key {
74 match pk {
75 PrimaryKeySyntax::Bool(true) => {
76 pk_columns.push(col.name.clone());
77 }
78 PrimaryKeySyntax::Bool(false) => {}
79 PrimaryKeySyntax::Object(pk_def) => {
80 pk_columns.push(col.name.clone());
81 if pk_def.auto_increment {
82 pk_auto_increment = true;
83 }
84 }
85 }
86 }
87 }
88
89 if !pk_columns.is_empty() {
91 let has_pk_constraint = constraints
92 .iter()
93 .any(|c| matches!(c, TableConstraint::PrimaryKey { .. }));
94
95 if !has_pk_constraint {
96 constraints.push(TableConstraint::PrimaryKey {
97 auto_increment: pk_auto_increment,
98 columns: pk_columns,
99 });
100 }
101 }
102
103 for col in &self.columns {
105 if let Some(ref unique_val) = col.unique {
107 match unique_val {
108 StrOrBoolOrArray::Str(name) => {
109 let constraint_name = Some(name.clone());
110
111 let exists = constraints.iter().any(|c| {
113 if let TableConstraint::Unique {
114 name: c_name,
115 columns,
116 } = c
117 {
118 c_name.as_ref() == Some(name)
119 && columns.len() == 1
120 && columns[0] == col.name
121 } else {
122 false
123 }
124 });
125
126 if !exists {
127 constraints.push(TableConstraint::Unique {
128 name: constraint_name,
129 columns: vec![col.name.clone()],
130 });
131 }
132 }
133 StrOrBoolOrArray::Bool(true) => {
134 let exists = constraints.iter().any(|c| {
135 if let TableConstraint::Unique {
136 name: None,
137 columns,
138 } = c
139 {
140 columns.len() == 1 && columns[0] == col.name
141 } else {
142 false
143 }
144 });
145
146 if !exists {
147 constraints.push(TableConstraint::Unique {
148 name: None,
149 columns: vec![col.name.clone()],
150 });
151 }
152 }
153 StrOrBoolOrArray::Bool(false) => continue,
154 StrOrBoolOrArray::Array(names) => {
155 for constraint_name in names {
158 if let Some(existing) = constraints.iter_mut().find(|c| {
160 if let TableConstraint::Unique { name: Some(n), .. } = c {
161 n == constraint_name
162 } else {
163 false
164 }
165 }) {
166 if let TableConstraint::Unique { columns, .. } = existing
168 && !columns.contains(&col.name)
169 {
170 columns.push(col.name.clone());
171 }
172 } else {
173 constraints.push(TableConstraint::Unique {
175 name: Some(constraint_name.clone()),
176 columns: vec![col.name.clone()],
177 });
178 }
179 }
180 }
181 }
182 }
183
184 if let Some(ref fk_syntax) = col.foreign_key {
186 let (ref_table, ref_columns, on_delete, on_update) = match fk_syntax {
188 ForeignKeySyntax::String(s) => {
189 let parts: Vec<&str> = s.split('.').collect();
191 if parts.len() != 2 || parts[0].is_empty() || parts[1].is_empty() {
192 return Err(TableValidationError::InvalidForeignKeyFormat {
193 column_name: col.name.clone(),
194 value: s.clone(),
195 });
196 }
197 (parts[0].to_string(), vec![parts[1].to_string()], None, None)
198 }
199 ForeignKeySyntax::Object(fk_def) => (
200 fk_def.ref_table.clone(),
201 fk_def.ref_columns.clone(),
202 fk_def.on_delete.clone(),
203 fk_def.on_update.clone(),
204 ),
205 };
206
207 let exists = constraints.iter().any(|c| {
209 if let TableConstraint::ForeignKey { columns, .. } = c {
210 columns.len() == 1 && columns[0] == col.name
211 } else {
212 false
213 }
214 });
215
216 if !exists {
217 constraints.push(TableConstraint::ForeignKey {
218 name: None,
219 columns: vec![col.name.clone()],
220 ref_table,
221 ref_columns,
222 on_delete,
223 on_update,
224 });
225 }
226 }
227 }
228
229 let mut index_groups: HashMap<String, Vec<String>> = HashMap::new();
232 let mut index_order: Vec<String> = Vec::new(); let mut inline_index_column_tracker: HashMap<String, HashSet<String>> = HashMap::new();
236
237 for col in &self.columns {
238 if let Some(ref index_val) = col.index {
239 match index_val {
240 StrOrBoolOrArray::Str(name) => {
241 let index_name = name.clone();
243
244 if let Some(columns) = inline_index_column_tracker.get(name.as_str())
246 && columns.contains(col.name.as_str())
247 {
248 return Err(TableValidationError::DuplicateIndexColumn {
249 index_name: name.clone(),
250 column_name: col.name.clone(),
251 });
252 }
253
254 if !index_groups.contains_key(&index_name) {
255 index_order.push(index_name.clone());
256 }
257
258 index_groups
259 .entry(index_name.clone())
260 .or_default()
261 .push(col.name.clone());
262
263 inline_index_column_tracker
264 .entry(index_name)
265 .or_default()
266 .insert(col.name.clone());
267 }
268 StrOrBoolOrArray::Bool(true) => {
269 let group_key = format!("__auto_{}", col.name);
273
274 if let Some(columns) = inline_index_column_tracker.get(group_key.as_str())
276 && columns.contains(col.name.as_str())
277 {
278 return Err(TableValidationError::DuplicateIndexColumn {
279 index_name: group_key.clone(),
280 column_name: col.name.clone(),
281 });
282 }
283
284 if !index_groups.contains_key(&group_key) {
285 index_order.push(group_key.clone());
286 }
287
288 index_groups
289 .entry(group_key.clone())
290 .or_default()
291 .push(col.name.clone());
292
293 inline_index_column_tracker
294 .entry(group_key)
295 .or_default()
296 .insert(col.name.clone());
297 }
298 StrOrBoolOrArray::Bool(false) => continue,
299 StrOrBoolOrArray::Array(names) => {
300 let mut seen_in_array = HashSet::new();
304 for index_name in names {
305 if seen_in_array.contains(index_name.as_str()) {
307 return Err(TableValidationError::DuplicateIndexColumn {
308 index_name: index_name.clone(),
309 column_name: col.name.clone(),
310 });
311 }
312 seen_in_array.insert(index_name.clone());
313
314 if let Some(columns) =
317 inline_index_column_tracker.get(index_name.as_str())
318 && columns.contains(col.name.as_str())
319 {
320 return Err(TableValidationError::DuplicateIndexColumn {
321 index_name: index_name.clone(),
322 column_name: col.name.clone(),
323 });
324 }
325
326 if !index_groups.contains_key(index_name.as_str()) {
327 index_order.push(index_name.clone());
328 }
329
330 index_groups
331 .entry(index_name.clone())
332 .or_default()
333 .push(col.name.clone());
334
335 inline_index_column_tracker
336 .entry(index_name.clone())
337 .or_default()
338 .insert(col.name.clone());
339 }
340 }
341 }
342 }
343 }
344
345 for index_name in index_order {
347 let columns = index_groups.get(&index_name).unwrap().clone();
348
349 let constraint_name = if index_name.starts_with("__auto_") {
352 None
354 } else {
355 Some(index_name.clone())
357 };
358
359 let exists = constraints.iter().any(|c| {
361 if let TableConstraint::Index {
362 name,
363 columns: cols,
364 } = c
365 {
366 match (&constraint_name, name) {
368 (Some(n1), Some(n2)) => n1 == n2,
369 (None, None) => cols == &columns,
370 _ => false,
371 }
372 } else {
373 false
374 }
375 });
376
377 if !exists {
378 constraints.push(TableConstraint::Index {
379 name: constraint_name,
380 columns,
381 });
382 }
383 }
384
385 Ok(TableDef {
386 name: self.name.clone(),
387 columns: self.columns.clone(),
388 constraints,
389 })
390 }
391}
392
393#[cfg(test)]
394mod tests {
395 use super::*;
396 use crate::schema::column::{ColumnType, SimpleColumnType};
397 use crate::schema::foreign_key::{ForeignKeyDef, ForeignKeySyntax};
398 use crate::schema::primary_key::PrimaryKeySyntax;
399 use crate::schema::reference::ReferenceAction;
400 use crate::schema::str_or_bool::StrOrBoolOrArray;
401
402 fn col(name: &str, ty: ColumnType) -> ColumnDef {
403 ColumnDef {
404 name: name.to_string(),
405 r#type: ty,
406 nullable: true,
407 default: None,
408 comment: None,
409 primary_key: None,
410 unique: None,
411 index: None,
412 foreign_key: None,
413 }
414 }
415
416 #[test]
417 fn normalize_inline_primary_key() {
418 let mut id_col = col("id", ColumnType::Simple(SimpleColumnType::Integer));
419 id_col.primary_key = Some(PrimaryKeySyntax::Bool(true));
420
421 let table = TableDef {
422 name: "users".into(),
423 columns: vec![
424 id_col,
425 col("name", ColumnType::Simple(SimpleColumnType::Text)),
426 ],
427 constraints: vec![],
428 };
429
430 let normalized = table.normalize().unwrap();
431 assert_eq!(normalized.constraints.len(), 1);
432 assert!(matches!(
433 &normalized.constraints[0],
434 TableConstraint::PrimaryKey { columns, .. } if columns == &["id".to_string()]
435 ));
436 }
437
438 #[test]
439 fn normalize_multiple_inline_primary_keys() {
440 let mut id_col = col("id", ColumnType::Simple(SimpleColumnType::Integer));
441 id_col.primary_key = Some(PrimaryKeySyntax::Bool(true));
442
443 let mut tenant_col = col("tenant_id", ColumnType::Simple(SimpleColumnType::Integer));
444 tenant_col.primary_key = Some(PrimaryKeySyntax::Bool(true));
445
446 let table = TableDef {
447 name: "users".into(),
448 columns: vec![id_col, tenant_col],
449 constraints: vec![],
450 };
451
452 let normalized = table.normalize().unwrap();
453 assert_eq!(normalized.constraints.len(), 1);
454 assert!(matches!(
455 &normalized.constraints[0],
456 TableConstraint::PrimaryKey { columns, .. } if columns == &["id".to_string(), "tenant_id".to_string()]
457 ));
458 }
459
460 #[test]
461 fn normalize_does_not_duplicate_existing_pk() {
462 let mut id_col = col("id", ColumnType::Simple(SimpleColumnType::Integer));
463 id_col.primary_key = Some(PrimaryKeySyntax::Bool(true));
464
465 let table = TableDef {
466 name: "users".into(),
467 columns: vec![id_col],
468 constraints: vec![TableConstraint::PrimaryKey {
469 auto_increment: false,
470 columns: vec!["id".into()],
471 }],
472 };
473
474 let normalized = table.normalize().unwrap();
475 assert_eq!(normalized.constraints.len(), 1);
476 }
477
478 #[test]
479 fn normalize_ignores_primary_key_false() {
480 let mut id_col = col("id", ColumnType::Simple(SimpleColumnType::Integer));
481 id_col.primary_key = Some(PrimaryKeySyntax::Bool(false));
482
483 let table = TableDef {
484 name: "users".into(),
485 columns: vec![
486 id_col,
487 col("name", ColumnType::Simple(SimpleColumnType::Text)),
488 ],
489 constraints: vec![],
490 };
491
492 let normalized = table.normalize().unwrap();
493 assert_eq!(normalized.constraints.len(), 0);
495 }
496
497 #[test]
498 fn normalize_inline_unique_bool() {
499 let mut email_col = col("email", ColumnType::Simple(SimpleColumnType::Text));
500 email_col.unique = Some(StrOrBoolOrArray::Bool(true));
501
502 let table = TableDef {
503 name: "users".into(),
504 columns: vec![
505 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
506 email_col,
507 ],
508 constraints: vec![],
509 };
510
511 let normalized = table.normalize().unwrap();
512 assert_eq!(normalized.constraints.len(), 1);
513 assert!(matches!(
514 &normalized.constraints[0],
515 TableConstraint::Unique { name: None, columns } if columns == &["email".to_string()]
516 ));
517 }
518
519 #[test]
520 fn normalize_inline_unique_with_name() {
521 let mut email_col = col("email", ColumnType::Simple(SimpleColumnType::Text));
522 email_col.unique = Some(StrOrBoolOrArray::Str("uq_users_email".into()));
523
524 let table = TableDef {
525 name: "users".into(),
526 columns: vec![
527 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
528 email_col,
529 ],
530 constraints: vec![],
531 };
532
533 let normalized = table.normalize().unwrap();
534 assert_eq!(normalized.constraints.len(), 1);
535 assert!(matches!(
536 &normalized.constraints[0],
537 TableConstraint::Unique { name: Some(n), columns }
538 if n == "uq_users_email" && columns == &["email".to_string()]
539 ));
540 }
541
542 #[test]
543 fn normalize_inline_index_bool() {
544 let mut name_col = col("name", ColumnType::Simple(SimpleColumnType::Text));
545 name_col.index = Some(StrOrBoolOrArray::Bool(true));
546
547 let table = TableDef {
548 name: "users".into(),
549 columns: vec![
550 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
551 name_col,
552 ],
553 constraints: vec![],
554 };
555
556 let normalized = table.normalize().unwrap();
557 let indexes: Vec<_> = normalized
559 .constraints
560 .iter()
561 .filter(|c| matches!(c, TableConstraint::Index { .. }))
562 .collect();
563 assert_eq!(indexes.len(), 1);
564 assert!(matches!(
567 indexes[0],
568 TableConstraint::Index { name: None, columns }
569 if columns == &["name".to_string()]
570 ));
571 }
572
573 #[test]
574 fn normalize_inline_index_with_name() {
575 let mut name_col = col("name", ColumnType::Simple(SimpleColumnType::Text));
576 name_col.index = Some(StrOrBoolOrArray::Str("custom_idx_name".into()));
577
578 let table = TableDef {
579 name: "users".into(),
580 columns: vec![
581 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
582 name_col,
583 ],
584 constraints: vec![],
585 };
586
587 let normalized = table.normalize().unwrap();
588 let indexes: Vec<_> = normalized
589 .constraints
590 .iter()
591 .filter(|c| matches!(c, TableConstraint::Index { .. }))
592 .collect();
593 assert_eq!(indexes.len(), 1);
594 assert!(matches!(
595 indexes[0],
596 TableConstraint::Index { name: Some(n), .. }
597 if n == "custom_idx_name"
598 ));
599 }
600
601 #[test]
602 fn normalize_inline_foreign_key() {
603 let mut user_id_col = col("user_id", ColumnType::Simple(SimpleColumnType::Integer));
604 user_id_col.foreign_key = Some(ForeignKeySyntax::Object(ForeignKeyDef {
605 ref_table: "users".into(),
606 ref_columns: vec!["id".into()],
607 on_delete: Some(ReferenceAction::Cascade),
608 on_update: None,
609 }));
610
611 let table = TableDef {
612 name: "posts".into(),
613 columns: vec![
614 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
615 user_id_col,
616 ],
617 constraints: vec![],
618 };
619
620 let normalized = table.normalize().unwrap();
621 assert_eq!(normalized.constraints.len(), 1);
622 assert!(matches!(
623 &normalized.constraints[0],
624 TableConstraint::ForeignKey {
625 name: None,
626 columns,
627 ref_table,
628 ref_columns,
629 on_delete: Some(ReferenceAction::Cascade),
630 on_update: None,
631 } if columns == &["user_id".to_string()]
632 && ref_table == "users"
633 && ref_columns == &["id".to_string()]
634 ));
635 }
636
637 #[test]
638 fn normalize_all_inline_constraints() {
639 let mut id_col = col("id", ColumnType::Simple(SimpleColumnType::Integer));
640 id_col.primary_key = Some(PrimaryKeySyntax::Bool(true));
641
642 let mut email_col = col("email", ColumnType::Simple(SimpleColumnType::Text));
643 email_col.unique = Some(StrOrBoolOrArray::Bool(true));
644
645 let mut name_col = col("name", ColumnType::Simple(SimpleColumnType::Text));
646 name_col.index = Some(StrOrBoolOrArray::Bool(true));
647
648 let mut user_id_col = col("org_id", ColumnType::Simple(SimpleColumnType::Integer));
649 user_id_col.foreign_key = Some(ForeignKeySyntax::Object(ForeignKeyDef {
650 ref_table: "orgs".into(),
651 ref_columns: vec!["id".into()],
652 on_delete: None,
653 on_update: None,
654 }));
655
656 let table = TableDef {
657 name: "users".into(),
658 columns: vec![id_col, email_col, name_col, user_id_col],
659 constraints: vec![],
660 };
661
662 let normalized = table.normalize().unwrap();
663 let non_index_constraints: Vec<_> = normalized
666 .constraints
667 .iter()
668 .filter(|c| !matches!(c, TableConstraint::Index { .. }))
669 .collect();
670 assert_eq!(non_index_constraints.len(), 3);
671 let indexes: Vec<_> = normalized
673 .constraints
674 .iter()
675 .filter(|c| matches!(c, TableConstraint::Index { .. }))
676 .collect();
677 assert_eq!(indexes.len(), 1);
678 }
679
680 #[test]
681 fn normalize_composite_index_from_string_name() {
682 let mut updated_at_col = col(
683 "updated_at",
684 ColumnType::Simple(SimpleColumnType::Timestamp),
685 );
686 updated_at_col.index = Some(StrOrBoolOrArray::Str("tuple".into()));
687
688 let mut user_id_col = col("user_id", ColumnType::Simple(SimpleColumnType::Integer));
689 user_id_col.index = Some(StrOrBoolOrArray::Str("tuple".into()));
690
691 let table = TableDef {
692 name: "post".into(),
693 columns: vec![
694 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
695 updated_at_col,
696 user_id_col,
697 ],
698 constraints: vec![],
699 };
700
701 let normalized = table.normalize().unwrap();
702 let indexes: Vec<_> = normalized
703 .constraints
704 .iter()
705 .filter_map(|c| {
706 if let TableConstraint::Index { name, columns } = c {
707 Some((name.clone(), columns.clone()))
708 } else {
709 None
710 }
711 })
712 .collect();
713 assert_eq!(indexes.len(), 1);
714 assert_eq!(indexes[0].0, Some("tuple".to_string()));
715 assert_eq!(
716 indexes[0].1,
717 vec!["updated_at".to_string(), "user_id".to_string()]
718 );
719 }
720
721 #[test]
722 fn normalize_multiple_different_indexes() {
723 let mut col1 = col("col1", ColumnType::Simple(SimpleColumnType::Text));
724 col1.index = Some(StrOrBoolOrArray::Str("idx_a".into()));
725
726 let mut col2 = col("col2", ColumnType::Simple(SimpleColumnType::Text));
727 col2.index = Some(StrOrBoolOrArray::Str("idx_a".into()));
728
729 let mut col3 = col("col3", ColumnType::Simple(SimpleColumnType::Text));
730 col3.index = Some(StrOrBoolOrArray::Str("idx_b".into()));
731
732 let mut col4 = col("col4", ColumnType::Simple(SimpleColumnType::Text));
733 col4.index = Some(StrOrBoolOrArray::Bool(true));
734
735 let table = TableDef {
736 name: "test".into(),
737 columns: vec![
738 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
739 col1,
740 col2,
741 col3,
742 col4,
743 ],
744 constraints: vec![],
745 };
746
747 let normalized = table.normalize().unwrap();
748 let indexes: Vec<_> = normalized
749 .constraints
750 .iter()
751 .filter_map(|c| {
752 if let TableConstraint::Index { name, columns } = c {
753 Some((name.clone(), columns.clone()))
754 } else {
755 None
756 }
757 })
758 .collect();
759 assert_eq!(indexes.len(), 3);
760
761 let idx_a = indexes
763 .iter()
764 .find(|(n, _)| n == &Some("idx_a".to_string()))
765 .unwrap();
766 assert_eq!(idx_a.1, vec!["col1".to_string(), "col2".to_string()]);
767
768 let idx_b = indexes
770 .iter()
771 .find(|(n, _)| n == &Some("idx_b".to_string()))
772 .unwrap();
773 assert_eq!(idx_b.1, vec!["col3".to_string()]);
774
775 let idx_col4 = indexes.iter().find(|(n, _)| n.is_none()).unwrap();
777 assert_eq!(idx_col4.1, vec!["col4".to_string()]);
778 }
779
780 #[test]
781 fn normalize_false_values_are_ignored() {
782 let mut email_col = col("email", ColumnType::Simple(SimpleColumnType::Text));
783 email_col.unique = Some(StrOrBoolOrArray::Bool(false));
784 email_col.index = Some(StrOrBoolOrArray::Bool(false));
785
786 let table = TableDef {
787 name: "users".into(),
788 columns: vec![
789 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
790 email_col,
791 ],
792 constraints: vec![],
793 };
794
795 let normalized = table.normalize().unwrap();
796 assert_eq!(normalized.constraints.len(), 0);
797 }
798
799 #[test]
800 fn normalize_table_without_primary_key() {
801 let table = TableDef {
804 name: "users".into(),
805 columns: vec![
806 col("name", ColumnType::Simple(SimpleColumnType::Text)),
807 col("email", ColumnType::Simple(SimpleColumnType::Text)),
808 ],
809 constraints: vec![],
810 };
811
812 let normalized = table.normalize().unwrap();
813 assert_eq!(normalized.constraints.len(), 0);
815 }
816
817 #[test]
818 fn normalize_multiple_indexes_from_same_array() {
819 let mut updated_at_col = col(
821 "updated_at",
822 ColumnType::Simple(SimpleColumnType::Timestamp),
823 );
824 updated_at_col.index = Some(StrOrBoolOrArray::Array(vec![
825 "tuple".into(),
826 "tuple2".into(),
827 ]));
828
829 let mut user_id_col = col("user_id", ColumnType::Simple(SimpleColumnType::Integer));
830 user_id_col.index = Some(StrOrBoolOrArray::Array(vec![
831 "tuple".into(),
832 "tuple2".into(),
833 ]));
834
835 let table = TableDef {
836 name: "post".into(),
837 columns: vec![
838 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
839 updated_at_col,
840 user_id_col,
841 ],
842 constraints: vec![],
843 };
844
845 let normalized = table.normalize().unwrap();
846 let indexes: Vec<_> = normalized
848 .constraints
849 .iter()
850 .filter_map(|c| {
851 if let TableConstraint::Index { name, columns } = c {
852 Some((name.clone(), columns.clone()))
853 } else {
854 None
855 }
856 })
857 .collect();
858 assert_eq!(indexes.len(), 2);
859
860 let tuple_idx = indexes
861 .iter()
862 .find(|(n, _)| n == &Some("tuple".to_string()))
863 .unwrap();
864 let mut sorted_cols = tuple_idx.1.clone();
865 sorted_cols.sort();
866 assert_eq!(
867 sorted_cols,
868 vec!["updated_at".to_string(), "user_id".to_string()]
869 );
870
871 let tuple2_idx = indexes
872 .iter()
873 .find(|(n, _)| n == &Some("tuple2".to_string()))
874 .unwrap();
875 let mut sorted_cols2 = tuple2_idx.1.clone();
876 sorted_cols2.sort();
877 assert_eq!(
878 sorted_cols2,
879 vec!["updated_at".to_string(), "user_id".to_string()]
880 );
881 }
882
883 #[test]
884 fn normalize_inline_unique_with_array_existing_constraint() {
885 let mut col1 = col("col1", ColumnType::Simple(SimpleColumnType::Text));
887 col1.unique = Some(StrOrBoolOrArray::Array(vec!["uq_group".into()]));
888
889 let mut col2 = col("col2", ColumnType::Simple(SimpleColumnType::Text));
890 col2.unique = Some(StrOrBoolOrArray::Array(vec!["uq_group".into()]));
891
892 let table = TableDef {
893 name: "test".into(),
894 columns: vec![
895 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
896 col1,
897 col2,
898 ],
899 constraints: vec![],
900 };
901
902 let normalized = table.normalize().unwrap();
903 assert_eq!(normalized.constraints.len(), 1);
904 let unique_constraint = &normalized.constraints[0];
905 assert!(matches!(
906 unique_constraint,
907 TableConstraint::Unique { name: Some(n), columns: _ }
908 if n == "uq_group"
909 ));
910 if let TableConstraint::Unique { columns, .. } = unique_constraint {
911 let mut sorted_cols = columns.clone();
912 sorted_cols.sort();
913 assert_eq!(sorted_cols, vec!["col1".to_string(), "col2".to_string()]);
914 }
915 }
916
917 #[test]
918 fn normalize_inline_unique_with_array_column_already_in_constraint() {
919 let mut col1 = col("col1", ColumnType::Simple(SimpleColumnType::Text));
921 col1.unique = Some(StrOrBoolOrArray::Array(vec!["uq_group".into()]));
922
923 let table = TableDef {
924 name: "test".into(),
925 columns: vec![
926 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
927 col1.clone(),
928 ],
929 constraints: vec![],
930 };
931
932 let normalized1 = table.normalize().unwrap();
933 assert_eq!(normalized1.constraints.len(), 1);
934
935 let table2 = TableDef {
937 name: "test".into(),
938 columns: vec![
939 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
940 col1,
941 ],
942 constraints: normalized1.constraints.clone(),
943 };
944
945 let normalized2 = table2.normalize().unwrap();
946 assert_eq!(normalized2.constraints.len(), 1);
947 if let TableConstraint::Unique { columns, .. } = &normalized2.constraints[0] {
948 assert_eq!(columns.len(), 1);
949 assert_eq!(columns[0], "col1");
950 }
951 }
952
953 #[test]
954 fn normalize_inline_unique_str_already_exists() {
955 let mut email_col = col("email", ColumnType::Simple(SimpleColumnType::Text));
957 email_col.unique = Some(StrOrBoolOrArray::Str("uq_email".into()));
958
959 let table = TableDef {
960 name: "users".into(),
961 columns: vec![
962 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
963 email_col,
964 ],
965 constraints: vec![TableConstraint::Unique {
966 name: Some("uq_email".into()),
967 columns: vec!["email".into()],
968 }],
969 };
970
971 let normalized = table.normalize().unwrap();
972 let unique_constraints: Vec<_> = normalized
974 .constraints
975 .iter()
976 .filter(|c| matches!(c, TableConstraint::Unique { .. }))
977 .collect();
978 assert_eq!(unique_constraints.len(), 1);
979 }
980
981 #[test]
982 fn normalize_inline_unique_bool_already_exists() {
983 let mut email_col = col("email", ColumnType::Simple(SimpleColumnType::Text));
985 email_col.unique = Some(StrOrBoolOrArray::Bool(true));
986
987 let table = TableDef {
988 name: "users".into(),
989 columns: vec![
990 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
991 email_col,
992 ],
993 constraints: vec![TableConstraint::Unique {
994 name: None,
995 columns: vec!["email".into()],
996 }],
997 };
998
999 let normalized = table.normalize().unwrap();
1000 let unique_constraints: Vec<_> = normalized
1002 .constraints
1003 .iter()
1004 .filter(|c| matches!(c, TableConstraint::Unique { .. }))
1005 .collect();
1006 assert_eq!(unique_constraints.len(), 1);
1007 }
1008
1009 #[test]
1010 fn normalize_inline_foreign_key_already_exists() {
1011 let mut user_id_col = col("user_id", ColumnType::Simple(SimpleColumnType::Integer));
1013 user_id_col.foreign_key = Some(ForeignKeySyntax::Object(ForeignKeyDef {
1014 ref_table: "users".into(),
1015 ref_columns: vec!["id".into()],
1016 on_delete: None,
1017 on_update: None,
1018 }));
1019
1020 let table = TableDef {
1021 name: "posts".into(),
1022 columns: vec![
1023 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
1024 user_id_col,
1025 ],
1026 constraints: vec![TableConstraint::ForeignKey {
1027 name: None,
1028 columns: vec!["user_id".into()],
1029 ref_table: "users".into(),
1030 ref_columns: vec!["id".into()],
1031 on_delete: None,
1032 on_update: None,
1033 }],
1034 };
1035
1036 let normalized = table.normalize().unwrap();
1037 let fk_constraints: Vec<_> = normalized
1039 .constraints
1040 .iter()
1041 .filter(|c| matches!(c, TableConstraint::ForeignKey { .. }))
1042 .collect();
1043 assert_eq!(fk_constraints.len(), 1);
1044 }
1045
1046 #[test]
1047 fn normalize_duplicate_index_same_column_str() {
1048 let mut col1 = col("col1", ColumnType::Simple(SimpleColumnType::Text));
1051 col1.index = Some(StrOrBoolOrArray::Str("idx1".into()));
1052
1053 let table = TableDef {
1054 name: "test".into(),
1055 columns: vec![
1056 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
1057 col1.clone(),
1058 {
1059 let mut c = col1.clone();
1061 c.index = Some(StrOrBoolOrArray::Str("idx1".into()));
1062 c
1063 },
1064 ],
1065 constraints: vec![],
1066 };
1067
1068 let result = table.normalize();
1069 assert!(result.is_err());
1070 if let Err(TableValidationError::DuplicateIndexColumn {
1071 index_name,
1072 column_name,
1073 }) = result
1074 {
1075 assert_eq!(index_name, "idx1");
1076 assert_eq!(column_name, "col1");
1077 } else {
1078 panic!("Expected DuplicateIndexColumn error");
1079 }
1080 }
1081
1082 #[test]
1083 fn normalize_duplicate_index_same_column_array() {
1084 let mut col1 = col("col1", ColumnType::Simple(SimpleColumnType::Text));
1086 col1.index = Some(StrOrBoolOrArray::Array(vec!["idx1".into(), "idx1".into()]));
1087
1088 let table = TableDef {
1089 name: "test".into(),
1090 columns: vec![
1091 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
1092 col1,
1093 ],
1094 constraints: vec![],
1095 };
1096
1097 let result = table.normalize();
1098 assert!(result.is_err());
1099 if let Err(TableValidationError::DuplicateIndexColumn {
1100 index_name,
1101 column_name,
1102 }) = result
1103 {
1104 assert_eq!(index_name, "idx1");
1105 assert_eq!(column_name, "col1");
1106 } else {
1107 panic!("Expected DuplicateIndexColumn error");
1108 }
1109 }
1110
1111 #[test]
1112 fn normalize_duplicate_index_same_column_multiple_definitions() {
1113 let mut col1 = col("col1", ColumnType::Simple(SimpleColumnType::Text));
1115 col1.index = Some(StrOrBoolOrArray::Str("idx1".into()));
1116
1117 let table = TableDef {
1118 name: "test".into(),
1119 columns: vec![
1120 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
1121 col1.clone(),
1122 {
1123 let mut c = col1.clone();
1124 c.index = Some(StrOrBoolOrArray::Array(vec!["idx1".into()]));
1125 c
1126 },
1127 ],
1128 constraints: vec![],
1129 };
1130
1131 let result = table.normalize();
1132 assert!(result.is_err());
1133 if let Err(TableValidationError::DuplicateIndexColumn {
1134 index_name,
1135 column_name,
1136 }) = result
1137 {
1138 assert_eq!(index_name, "idx1");
1139 assert_eq!(column_name, "col1");
1140 } else {
1141 panic!("Expected DuplicateIndexColumn error");
1142 }
1143 }
1144
1145 #[test]
1146 fn test_table_validation_error_display() {
1147 let error = TableValidationError::DuplicateIndexColumn {
1148 index_name: "idx_test".into(),
1149 column_name: "col1".into(),
1150 };
1151 let error_msg = format!("{}", error);
1152 assert!(error_msg.contains("idx_test"));
1153 assert!(error_msg.contains("col1"));
1154 assert!(error_msg.contains("Duplicate index"));
1155 }
1156
1157 #[test]
1158 fn normalize_inline_unique_str_with_different_constraint_type() {
1159 let mut email_col = col("email", ColumnType::Simple(SimpleColumnType::Text));
1161 email_col.unique = Some(StrOrBoolOrArray::Str("uq_email".into()));
1162
1163 let table = TableDef {
1164 name: "users".into(),
1165 columns: vec![
1166 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
1167 email_col,
1168 ],
1169 constraints: vec![
1170 TableConstraint::PrimaryKey {
1172 auto_increment: false,
1173 columns: vec!["id".into()],
1174 },
1175 ],
1176 };
1177
1178 let normalized = table.normalize().unwrap();
1179 assert_eq!(normalized.constraints.len(), 2);
1181 }
1182
1183 #[test]
1184 fn normalize_inline_unique_array_with_different_constraint_type() {
1185 let mut col1 = col("col1", ColumnType::Simple(SimpleColumnType::Text));
1187 col1.unique = Some(StrOrBoolOrArray::Array(vec!["uq_group".into()]));
1188
1189 let table = TableDef {
1190 name: "test".into(),
1191 columns: vec![
1192 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
1193 col1,
1194 ],
1195 constraints: vec![
1196 TableConstraint::PrimaryKey {
1198 auto_increment: false,
1199 columns: vec!["id".into()],
1200 },
1201 ],
1202 };
1203
1204 let normalized = table.normalize().unwrap();
1205 assert_eq!(normalized.constraints.len(), 2);
1207 }
1208
1209 #[test]
1210 fn normalize_duplicate_index_bool_true_same_column() {
1211 let mut col1 = col("col1", ColumnType::Simple(SimpleColumnType::Text));
1213 col1.index = Some(StrOrBoolOrArray::Bool(true));
1214
1215 let table = TableDef {
1216 name: "test".into(),
1217 columns: vec![
1218 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
1219 col1.clone(),
1220 {
1221 let mut c = col1.clone();
1223 c.index = Some(StrOrBoolOrArray::Bool(true));
1224 c
1225 },
1226 ],
1227 constraints: vec![],
1228 };
1229
1230 let result = table.normalize();
1231 assert!(result.is_err());
1232 if let Err(TableValidationError::DuplicateIndexColumn {
1233 index_name,
1234 column_name,
1235 }) = result
1236 {
1237 assert!(index_name.contains("__auto_"));
1239 assert!(index_name.contains("col1"));
1240 assert_eq!(column_name, "col1");
1241 } else {
1242 panic!("Expected DuplicateIndexColumn error");
1243 }
1244 }
1245
1246 #[test]
1247 fn normalize_inline_foreign_key_string_syntax() {
1248 let mut user_id_col = col("user_id", ColumnType::Simple(SimpleColumnType::Integer));
1250 user_id_col.foreign_key = Some(ForeignKeySyntax::String("users.id".into()));
1251
1252 let table = TableDef {
1253 name: "posts".into(),
1254 columns: vec![
1255 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
1256 user_id_col,
1257 ],
1258 constraints: vec![],
1259 };
1260
1261 let normalized = table.normalize().unwrap();
1262 assert_eq!(normalized.constraints.len(), 1);
1263 assert!(matches!(
1264 &normalized.constraints[0],
1265 TableConstraint::ForeignKey {
1266 name: None,
1267 columns,
1268 ref_table,
1269 ref_columns,
1270 on_delete: None,
1271 on_update: None,
1272 } if columns == &["user_id".to_string()]
1273 && ref_table == "users"
1274 && ref_columns == &["id".to_string()]
1275 ));
1276 }
1277
1278 #[test]
1279 fn normalize_inline_foreign_key_invalid_format_no_dot() {
1280 let mut user_id_col = col("user_id", ColumnType::Simple(SimpleColumnType::Integer));
1282 user_id_col.foreign_key = Some(ForeignKeySyntax::String("usersid".into()));
1283
1284 let table = TableDef {
1285 name: "posts".into(),
1286 columns: vec![
1287 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
1288 user_id_col,
1289 ],
1290 constraints: vec![],
1291 };
1292
1293 let result = table.normalize();
1294 assert!(result.is_err());
1295 if let Err(TableValidationError::InvalidForeignKeyFormat { column_name, value }) = result {
1296 assert_eq!(column_name, "user_id");
1297 assert_eq!(value, "usersid");
1298 } else {
1299 panic!("Expected InvalidForeignKeyFormat error");
1300 }
1301 }
1302
1303 #[test]
1304 fn normalize_inline_foreign_key_invalid_format_empty_table() {
1305 let mut user_id_col = col("user_id", ColumnType::Simple(SimpleColumnType::Integer));
1307 user_id_col.foreign_key = Some(ForeignKeySyntax::String(".id".into()));
1308
1309 let table = TableDef {
1310 name: "posts".into(),
1311 columns: vec![
1312 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
1313 user_id_col,
1314 ],
1315 constraints: vec![],
1316 };
1317
1318 let result = table.normalize();
1319 assert!(result.is_err());
1320 if let Err(TableValidationError::InvalidForeignKeyFormat { column_name, value }) = result {
1321 assert_eq!(column_name, "user_id");
1322 assert_eq!(value, ".id");
1323 } else {
1324 panic!("Expected InvalidForeignKeyFormat error");
1325 }
1326 }
1327
1328 #[test]
1329 fn normalize_inline_foreign_key_invalid_format_empty_column() {
1330 let mut user_id_col = col("user_id", ColumnType::Simple(SimpleColumnType::Integer));
1332 user_id_col.foreign_key = Some(ForeignKeySyntax::String("users.".into()));
1333
1334 let table = TableDef {
1335 name: "posts".into(),
1336 columns: vec![
1337 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
1338 user_id_col,
1339 ],
1340 constraints: vec![],
1341 };
1342
1343 let result = table.normalize();
1344 assert!(result.is_err());
1345 if let Err(TableValidationError::InvalidForeignKeyFormat { column_name, value }) = result {
1346 assert_eq!(column_name, "user_id");
1347 assert_eq!(value, "users.");
1348 } else {
1349 panic!("Expected InvalidForeignKeyFormat error");
1350 }
1351 }
1352
1353 #[test]
1354 fn normalize_inline_foreign_key_invalid_format_too_many_parts() {
1355 let mut user_id_col = col("user_id", ColumnType::Simple(SimpleColumnType::Integer));
1357 user_id_col.foreign_key = Some(ForeignKeySyntax::String("schema.users.id".into()));
1358
1359 let table = TableDef {
1360 name: "posts".into(),
1361 columns: vec![
1362 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
1363 user_id_col,
1364 ],
1365 constraints: vec![],
1366 };
1367
1368 let result = table.normalize();
1369 assert!(result.is_err());
1370 if let Err(TableValidationError::InvalidForeignKeyFormat { column_name, value }) = result {
1371 assert_eq!(column_name, "user_id");
1372 assert_eq!(value, "schema.users.id");
1373 } else {
1374 panic!("Expected InvalidForeignKeyFormat error");
1375 }
1376 }
1377
1378 #[test]
1379 fn normalize_inline_primary_key_with_auto_increment() {
1380 use crate::schema::primary_key::PrimaryKeyDef;
1381
1382 let mut id_col = col("id", ColumnType::Simple(SimpleColumnType::Integer));
1383 id_col.primary_key = Some(PrimaryKeySyntax::Object(PrimaryKeyDef {
1384 auto_increment: true,
1385 columns: vec![], }));
1387
1388 let table = TableDef {
1389 name: "users".into(),
1390 columns: vec![
1391 id_col,
1392 col("name", ColumnType::Simple(SimpleColumnType::Text)),
1393 ],
1394 constraints: vec![],
1395 };
1396
1397 let normalized = table.normalize().unwrap();
1398 assert_eq!(normalized.constraints.len(), 1);
1399 assert!(matches!(
1400 &normalized.constraints[0],
1401 TableConstraint::PrimaryKey { auto_increment: true, columns } if columns == &["id".to_string()]
1402 ));
1403 }
1404
1405 #[test]
1406 fn normalize_duplicate_inline_index_on_same_column() {
1407 use crate::schema::str_or_bool::StrOrBoolOrArray;
1410
1411 let mut email_col = col("email", ColumnType::Simple(SimpleColumnType::Text));
1413 email_col.index = Some(StrOrBoolOrArray::Array(vec![
1414 "idx_email".into(),
1415 "idx_email".into(), ]));
1417
1418 let table = TableDef {
1419 name: "users".into(),
1420 columns: vec![
1421 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
1422 email_col,
1423 ],
1424 constraints: vec![],
1425 };
1426
1427 let result = table.normalize();
1428 assert!(result.is_err());
1429 if let Err(TableValidationError::DuplicateIndexColumn {
1430 index_name,
1431 column_name,
1432 }) = result
1433 {
1434 assert_eq!(index_name, "idx_email");
1435 assert_eq!(column_name, "email");
1436 } else {
1437 panic!("Expected DuplicateIndexColumn error, got: {:?}", result);
1438 }
1439 }
1440
1441 #[test]
1442 fn test_invalid_foreign_key_format_error_display() {
1443 let error = TableValidationError::InvalidForeignKeyFormat {
1444 column_name: "user_id".into(),
1445 value: "invalid".into(),
1446 };
1447 let error_msg = format!("{}", error);
1448 assert!(error_msg.contains("user_id"));
1449 assert!(error_msg.contains("invalid"));
1450 assert!(error_msg.contains("table.column"));
1451 }
1452}