1use vespertide_core::{MigrationPlan, TableDef};
2
3use crate::apply::apply_action;
4use crate::error::PlannerError;
5
6pub fn schema_from_plans(plans: &[MigrationPlan]) -> Result<Vec<TableDef>, PlannerError> {
8 let mut schema: Vec<TableDef> = Vec::new();
9 for plan in plans {
10 for action in &plan.actions {
11 apply_action(&mut schema, action)?;
12 }
13 }
14 Ok(schema)
15}
16
17#[cfg(test)]
18mod tests {
19 use super::*;
20 use rstest::rstest;
21 use vespertide_core::{
22 ColumnDef, ColumnType, MigrationAction, SimpleColumnType, TableConstraint,
23 };
24
25 fn col(name: &str, ty: ColumnType) -> ColumnDef {
26 ColumnDef {
27 name: name.to_string(),
28 r#type: ty,
29 nullable: true,
30 default: None,
31 comment: None,
32 primary_key: None,
33 unique: None,
34 index: None,
35 foreign_key: None,
36 }
37 }
38
39 fn table(name: &str, columns: Vec<ColumnDef>, constraints: Vec<TableConstraint>) -> TableDef {
40 TableDef {
41 name: name.to_string(),
42 columns,
43 constraints,
44 }
45 }
46
47 #[rstest]
48 #[case::create_only(
49 vec![MigrationPlan {
50 comment: None,
51 created_at: None,
52 version: 1,
53 actions: vec![MigrationAction::CreateTable {
54 table: "users".into(),
55 columns: vec![col("id", ColumnType::Simple(SimpleColumnType::Integer))],
56 constraints: vec![TableConstraint::PrimaryKey{ auto_increment: false, columns: vec!["id".into()] }],
57 }],
58 }],
59 table(
60 "users",
61 vec![col("id", ColumnType::Simple(SimpleColumnType::Integer))],
62 vec![TableConstraint::PrimaryKey{ auto_increment: false, columns: vec!["id".into()] }],
63 )
64 )]
65 #[case::create_and_add_column(
66 vec![
67 MigrationPlan {
68 comment: None,
69 created_at: None,
70 version: 1,
71 actions: vec![MigrationAction::CreateTable {
72 table: "users".into(),
73 columns: vec![col("id", ColumnType::Simple(SimpleColumnType::Integer))],
74 constraints: vec![TableConstraint::PrimaryKey{ auto_increment: false, columns: vec!["id".into()] }],
75 }],
76 },
77 MigrationPlan {
78 comment: None,
79 created_at: None,
80 version: 2,
81 actions: vec![MigrationAction::AddColumn {
82 table: "users".into(),
83 column: Box::new(col("name", ColumnType::Simple(SimpleColumnType::Text))),
84 fill_with: None,
85 }],
86 },
87 ],
88 table(
89 "users",
90 vec![
91 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
92 col("name", ColumnType::Simple(SimpleColumnType::Text)),
93 ],
94 vec![TableConstraint::PrimaryKey{ auto_increment: false, columns: vec!["id".into()] }],
95 )
96 )]
97 #[case::create_add_column_and_index(
98 vec![
99 MigrationPlan {
100 comment: None,
101 created_at: None,
102 version: 1,
103 actions: vec![MigrationAction::CreateTable {
104 table: "users".into(),
105 columns: vec![col("id", ColumnType::Simple(SimpleColumnType::Integer))],
106 constraints: vec![TableConstraint::PrimaryKey{ auto_increment: false, columns: vec!["id".into()] }],
107 }],
108 },
109 MigrationPlan {
110 comment: None,
111 created_at: None,
112 version: 2,
113 actions: vec![MigrationAction::AddColumn {
114 table: "users".into(),
115 column: Box::new(col("name", ColumnType::Simple(SimpleColumnType::Text))),
116 fill_with: None,
117 }],
118 },
119 MigrationPlan {
120 comment: None,
121 created_at: None,
122 version: 3,
123 actions: vec![MigrationAction::AddConstraint {
124 table: "users".into(),
125 constraint: TableConstraint::Index {
126 name: Some("ix_users__name".into()),
127 columns: vec!["name".into()],
128 },
129 }],
130 },
131 ],
132 table(
133 "users",
134 vec![
135 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
136 col("name", ColumnType::Simple(SimpleColumnType::Text)),
137 ],
138 vec![
139 TableConstraint::PrimaryKey{ auto_increment: false, columns: vec!["id".into()] },
140 TableConstraint::Index {
141 name: Some("ix_users__name".into()),
142 columns: vec!["name".into()],
143 },
144 ],
145 )
146 )]
147 fn schema_from_plans_applies_actions(
148 #[case] plans: Vec<MigrationPlan>,
149 #[case] expected_users: TableDef,
150 ) {
151 let schema = schema_from_plans(&plans).unwrap();
152 let users = schema.iter().find(|t| t.name == "users").unwrap();
153 assert_eq!(users, &expected_users);
154 }
155
156 #[test]
159 fn remove_constraint_with_inline_and_table_level_unique() {
160 use vespertide_core::StrOrBoolOrArray;
161
162 let create_plan = MigrationPlan {
164 comment: None,
165 created_at: None,
166 version: 1,
167 actions: vec![MigrationAction::CreateTable {
168 table: "users".into(),
169 columns: vec![ColumnDef {
170 name: "email".into(),
171 r#type: ColumnType::Simple(SimpleColumnType::Text),
172 nullable: false,
173 default: None,
174 comment: None,
175 primary_key: None,
176 unique: Some(StrOrBoolOrArray::Bool(true)), index: None,
178 foreign_key: None,
179 }],
180 constraints: vec![TableConstraint::Unique {
181 name: None,
182 columns: vec!["email".into()],
183 }], }],
185 };
186
187 let remove_plan = MigrationPlan {
189 comment: None,
190 created_at: None,
191 version: 2,
192 actions: vec![MigrationAction::RemoveConstraint {
193 table: "users".into(),
194 constraint: TableConstraint::Unique {
195 name: None,
196 columns: vec!["email".into()],
197 },
198 }],
199 };
200
201 let schema = schema_from_plans(&[create_plan, remove_plan]).unwrap();
202 let users = schema.iter().find(|t| t.name == "users").unwrap();
203
204 println!("Constraints after apply: {:?}", users.constraints);
205 println!("Column unique field: {:?}", users.columns[0].unique);
206
207 let normalized = users.clone().normalize().unwrap();
213 println!("Constraints after normalize: {:?}", normalized.constraints);
214
215 assert!(
222 normalized.constraints.is_empty(),
223 "Expected no constraints after normalize, but got: {:?}",
224 normalized.constraints
225 );
226 }
227}