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