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 description: None,
43 columns,
44 constraints,
45 }
46 }
47
48 #[rstest]
49 #[case::create_only(
50 vec![MigrationPlan {
51 comment: None,
52 created_at: None,
53 version: 1,
54 actions: vec![MigrationAction::CreateTable {
55 table: "users".into(),
56 columns: vec![col("id", ColumnType::Simple(SimpleColumnType::Integer))],
57 constraints: vec![TableConstraint::PrimaryKey{ auto_increment: false, columns: vec!["id".into()] }],
58 }],
59 }],
60 table(
61 "users",
62 vec![col("id", ColumnType::Simple(SimpleColumnType::Integer))],
63 vec![TableConstraint::PrimaryKey{ auto_increment: false, columns: vec!["id".into()] }],
64 )
65 )]
66 #[case::create_and_add_column(
67 vec![
68 MigrationPlan {
69 comment: None,
70 created_at: None,
71 version: 1,
72 actions: vec![MigrationAction::CreateTable {
73 table: "users".into(),
74 columns: vec![col("id", ColumnType::Simple(SimpleColumnType::Integer))],
75 constraints: vec![TableConstraint::PrimaryKey{ auto_increment: false, columns: vec!["id".into()] }],
76 }],
77 },
78 MigrationPlan {
79 comment: None,
80 created_at: None,
81 version: 2,
82 actions: vec![MigrationAction::AddColumn {
83 table: "users".into(),
84 column: Box::new(col("name", ColumnType::Simple(SimpleColumnType::Text))),
85 fill_with: None,
86 }],
87 },
88 ],
89 table(
90 "users",
91 vec![
92 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
93 col("name", ColumnType::Simple(SimpleColumnType::Text)),
94 ],
95 vec![TableConstraint::PrimaryKey{ auto_increment: false, columns: vec!["id".into()] }],
96 )
97 )]
98 #[case::create_add_column_and_index(
99 vec![
100 MigrationPlan {
101 comment: None,
102 created_at: None,
103 version: 1,
104 actions: vec![MigrationAction::CreateTable {
105 table: "users".into(),
106 columns: vec![col("id", ColumnType::Simple(SimpleColumnType::Integer))],
107 constraints: vec![TableConstraint::PrimaryKey{ auto_increment: false, columns: vec!["id".into()] }],
108 }],
109 },
110 MigrationPlan {
111 comment: None,
112 created_at: None,
113 version: 2,
114 actions: vec![MigrationAction::AddColumn {
115 table: "users".into(),
116 column: Box::new(col("name", ColumnType::Simple(SimpleColumnType::Text))),
117 fill_with: None,
118 }],
119 },
120 MigrationPlan {
121 comment: None,
122 created_at: None,
123 version: 3,
124 actions: vec![MigrationAction::AddConstraint {
125 table: "users".into(),
126 constraint: TableConstraint::Index {
127 name: Some("ix_users__name".into()),
128 columns: vec!["name".into()],
129 },
130 }],
131 },
132 ],
133 table(
134 "users",
135 vec![
136 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
137 col("name", ColumnType::Simple(SimpleColumnType::Text)),
138 ],
139 vec![
140 TableConstraint::PrimaryKey{ auto_increment: false, columns: vec!["id".into()] },
141 TableConstraint::Index {
142 name: Some("ix_users__name".into()),
143 columns: vec!["name".into()],
144 },
145 ],
146 )
147 )]
148 fn schema_from_plans_applies_actions(
149 #[case] plans: Vec<MigrationPlan>,
150 #[case] expected_users: TableDef,
151 ) {
152 let schema = schema_from_plans(&plans).unwrap();
153 let users = schema.iter().find(|t| t.name == "users").unwrap();
154 assert_eq!(users, &expected_users);
155 }
156
157 #[test]
160 fn remove_constraint_with_inline_and_table_level_unique() {
161 use vespertide_core::StrOrBoolOrArray;
162
163 let create_plan = MigrationPlan {
165 comment: None,
166 created_at: None,
167 version: 1,
168 actions: vec![MigrationAction::CreateTable {
169 table: "users".into(),
170 columns: vec![ColumnDef {
171 name: "email".into(),
172 r#type: ColumnType::Simple(SimpleColumnType::Text),
173 nullable: false,
174 default: None,
175 comment: None,
176 primary_key: None,
177 unique: Some(StrOrBoolOrArray::Bool(true)), index: None,
179 foreign_key: None,
180 }],
181 constraints: vec![TableConstraint::Unique {
182 name: None,
183 columns: vec!["email".into()],
184 }], }],
186 };
187
188 let remove_plan = MigrationPlan {
190 comment: None,
191 created_at: None,
192 version: 2,
193 actions: vec![MigrationAction::RemoveConstraint {
194 table: "users".into(),
195 constraint: TableConstraint::Unique {
196 name: None,
197 columns: vec!["email".into()],
198 },
199 }],
200 };
201
202 let schema = schema_from_plans(&[create_plan, remove_plan]).unwrap();
203 let users = schema.iter().find(|t| t.name == "users").unwrap();
204
205 println!("Constraints after apply: {:?}", users.constraints);
206 println!("Column unique field: {:?}", users.columns[0].unique);
207
208 let normalized = users.clone().normalize().unwrap();
214 println!("Constraints after normalize: {:?}", normalized.constraints);
215
216 assert!(
223 normalized.constraints.is_empty(),
224 "Expected no constraints after normalize, but got: {:?}",
225 normalized.constraints
226 );
227 }
228}