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