on main help sign in
Refactor tests to use test framework
9cac252fabianlindfors
Cargo.toml+1−2
@@ -19,5 +19,4 @@ anyhow = { version = "1.0.44", features = ["backtrace"] }
19clap = { version = "3.0.0", features = ["derive"] }
19clap = { version = "3.0.0", features = ["derive"] }
20toml = "0.5"
20toml = "0.5"
21version = "3.0.0"
21version = "3.0.0"
22−colored = "2"
22+colored = "2"
23−"derive_builder" = "0.10.2"
src/migrations/common.rs+1−10
@@ -1,26 +1,17 @@
1use anyhow::anyhow;
1use anyhow::anyhow;
2−use derive_builder::Builder;
3use postgres::types::{FromSql, ToSql};
2use postgres::types::{FromSql, ToSql};
4use serde::{Deserialize, Serialize};
3use serde::{Deserialize, Serialize};
5
4
6use crate::db::Conn;
5use crate::db::Conn;
7
6
8−#[derive(Serialize, Deserialize, Builder, Clone, Debug)]
7+#[derive(Serialize, Deserialize, Clone, Debug)]
9−#[builder(setter(into))]
10pub struct Column {
8pub struct Column {
11 pub name: String,
9 pub name: String,
12−
13 #[serde(rename = "type")]
10 #[serde(rename = "type")]
14 pub data_type: String,
11 pub data_type: String,
15−
16 #[serde(default = "nullable_default")]
12 #[serde(default = "nullable_default")]
17− #[builder(default = "true")]
18 pub nullable: bool,
13 pub nullable: bool,
19−
20− #[builder(setter(name = "default_value", strip_option), default)]
21 pub default: Option<String>,
14 pub default: Option<String>,
22−
23− #[builder(setter(strip_option), default)]
24 pub generated: Option<String>,
15 pub generated: Option<String>,
25}
16}
26
17
src/migrations/create_table.rs+1−4
@@ -4,18 +4,15 @@ use crate::{
4 schema::Schema,
4 schema::Schema,
5};
5};
6use anyhow::Context;
6use anyhow::Context;
7−use derive_builder::Builder;
8use serde::{Deserialize, Serialize};
7use serde::{Deserialize, Serialize};
9
8
10−#[derive(Serialize, Deserialize, Builder, Debug)]
9+#[derive(Serialize, Deserialize, Debug)]
11−#[builder(setter(into))]
12pub struct CreateTable {
10pub struct CreateTable {
13 pub name: String,
11 pub name: String,
14 pub columns: Vec<Column>,
12 pub columns: Vec<Column>,
15 pub primary_key: Vec<String>,
13 pub primary_key: Vec<String>,
16
14
17 #[serde(default)]
15 #[serde(default)]
18− #[builder(default)]
19 pub foreign_keys: Vec<ForeignKey>,
16 pub foreign_keys: Vec<ForeignKey>,
20}
17}
21
18
src/migrations/mod.rs+2−2
@@ -7,10 +7,10 @@ use serde::{Deserialize, Serialize};
7
7
8// Re-export migration types
8// Re-export migration types
9mod common;
9mod common;
10−pub use common::{Column, ColumnBuilder};
10+pub use common::Column;
11
11
12mod create_table;
12mod create_table;
13−pub use create_table::{CreateTable, CreateTableBuilder, ForeignKey};
13+pub use create_table::{CreateTable, ForeignKey};
14
14
15mod alter_column;
15mod alter_column;
16pub use alter_column::{AlterColumn, ColumnChanges};
16pub use alter_column::{AlterColumn, ColumnChanges};
tests/add_column.rs+126−153
@@ -1,10 +1,9 @@
1−use reshape::migrations::{AddColumn, Column, ColumnBuilder, CreateTableBuilder, Migration};
2−
3mod common;
1mod common;
2+use common::Test;
4
3
5#[test]
4#[test]
6fn add_column() {
5fn add_column() {
7− let mut test = common::Test::new("Add column");
6+ let mut test = Test::new("Add column");
8
7
9 test.first_migration(
8 test.first_migration(
10 r#"
9 r#"
@@ -114,176 +113,150 @@ fn add_column() {
114
113
115#[test]
114#[test]
116fn add_column_nullable() {
115fn add_column_nullable() {
117− let (mut reshape, mut old_db, mut new_db) = common::setup();
116+ let mut test = Test::new("Add nullable column");
118−
117+
118+ test.first_migration(
119+ r#"
119− let create_users_table = Migration::new("create_user_table", None).with_action(
120+ name = "create_users_table"
120− CreateTableBuilder::default()
121+
122+ [[actions]]
123+ type = "create_table"
121− .name("users")
124+ name = "users"
122− .primary_key(vec!["id".to_string()])
125+ primary_key = ["id"]
123− .columns(vec![ColumnBuilder::default()
126+
127+ [[actions.columns]]
124− .name("id")
128+ name = "id"
125− .data_type("INTEGER")
129+ type = "INTEGER"
126− .build()
130+ "#,
127− .unwrap()])
128− .build()
129− .unwrap(),
130 );
131 );
131− let add_name_column = Migration::new("add_nullable_name_column", None).with_action(AddColumn {
132− table: "users".to_string(),
133− column: Column {
134− name: "name".to_string(),
135− data_type: "TEXT".to_string(),
136− nullable: true,
137− default: None,
138− generated: None,
139− },
140− up: None,
141− });
142
132
143− let first_migrations = vec![create_users_table.clone()];
133+ test.second_migration(
144− let second_migrations = vec![create_users_table.clone(), add_name_column.clone()];
134+ r#"
135+ name = "add_nullable_name_column"
145
136
146− // Run first migration, should automatically finish
137+ [[actions]]
147− reshape.migrate(first_migrations.clone()).unwrap();
138+ type = "add_column"
139+ table = "users"
148
140
149− // Update search paths
141+ [actions.column]
150− old_db
142+ name = "name"
151− .simple_query(&reshape::schema_query_for_migration(
143+ type = "TEXT"
152− &first_migrations.last().unwrap().name,
144+ "#,
153− ))
145+ );
154− .unwrap();
155− new_db
156− .simple_query(&reshape::schema_query_for_migration(
157− &first_migrations.last().unwrap().name,
158− ))
159− .unwrap();
160
146
147+ test.after_first(|db| {
161− // Insert some test values
148+ // Insert some test values
162− new_db
163− .simple_query(
149+ db.simple_query(
164 "
150 "
165− INSERT INTO users (id) VALUES
151+ INSERT INTO users (id) VALUES (1), (2);
166− (1),
167− (2);
168 ",
152 ",
169 )
153 )
170 .unwrap();
154 .unwrap();
155+ });
171
156
172− // Run second migration
157+ test.intermediate(|old_db, new_db| {
173− reshape.migrate(second_migrations.clone()).unwrap();
158+ // Ensure existing data got updated
174− new_db
159+ let expected: Vec<Option<String>> = vec![None, None];
175− .simple_query(&reshape::schema_query_for_migration(
160+ assert!(new_db
176− &second_migrations.last().unwrap().name,
161+ .query("SELECT name FROM users ORDER BY id", &[],)
177− ))
162+ .unwrap()
178− .unwrap();
163+ .iter()
164+ .map(|row| row.get::<_, Option<String>>("name"))
165+ .eq(expected));
179
166
180− // Ensure existing data got updated
181− let expected: Vec<Option<String>> = vec![None, None];
182− assert!(new_db
183− .query("SELECT name FROM users ORDER BY id", &[],)
184− .unwrap()
185− .iter()
186− .map(|row| row.get::<_, Option<String>>("name"))
187− .eq(expected));
188−
189− // Insert data using old schema and ensure new column is NULL
167+ // Insert data using old schema and ensure new column is NULL
190− old_db
168+ old_db
191− .simple_query("INSERT INTO users (id) VALUES (3)")
169+ .simple_query("INSERT INTO users (id) VALUES (3)")
192− .unwrap();
170+ .unwrap();
193− let name: Option<String> = new_db
171+ let name: Option<String> = new_db
194− .query_one("SELECT name from users WHERE id = 3", &[])
172+ .query_one("SELECT name from users WHERE id = 3", &[])
195− .map(|row| (row.get("name")))
173+ .map(|row| (row.get("name")))
196− .unwrap();
174+ .unwrap();
197− assert_eq!(None, name);
175+ assert_eq!(None, name);
198
176
199− // Ensure data can be inserted against new schema
177+ // Ensure data can be inserted against new schema
200− new_db
178+ new_db
201− .simple_query("INSERT INTO users (id, name) VALUES (4, 'Test Testsson'), (5, NULL)")
179+ .simple_query("INSERT INTO users (id, name) VALUES (4, 'Test Testsson'), (5, NULL)")
180+ .unwrap();
181+ });
182+
183+ test.after_completion(|db| {
184+ let expected: Vec<Option<String>> =
185+ vec![None, None, None, Some("Test Testsson".to_string()), None];
186+ let result: Vec<Option<String>> = db
187+ .query("SELECT id, name FROM users ORDER BY id", &[])
202− .unwrap();
188+ .unwrap()
189+ .iter()
190+ .map(|row| row.get("name"))
191+ .collect();
203
192
204− reshape.complete().unwrap();
193+ assert_eq!(result, expected);
205− common::assert_cleaned_up(&mut new_db);
194+ });
195+
196+ test.run();
206}
197}
207
198
208#[test]
199#[test]
209fn add_column_with_default() {
200fn add_column_with_default() {
210− let (mut reshape, mut old_db, mut new_db) = common::setup();
201+ let mut test = Test::new("Add column with default value");
211−
202+
203+ test.first_migration(
204+ r#"
212− let create_users_table = Migration::new("create_user_table", None).with_action(
205+ name = "create_users_table"
213− CreateTableBuilder::default()
206+
207+ [[actions]]
208+ type = "create_table"
214− .name("users")
209+ name = "users"
215− .primary_key(vec!["id".to_string()])
210+ primary_key = ["id"]
216− .columns(vec![ColumnBuilder::default()
211+
212+ [[actions.columns]]
217− .name("id")
213+ name = "id"
218− .data_type("INTEGER")
214+ type = "INTEGER"
219− .build()
215+ "#,
220− .unwrap()])
221− .build()
222− .unwrap(),
223 );
216 );
224− let add_name_column =
225− Migration::new("add_name_column_with_default", None).with_action(AddColumn {
226− table: "users".to_string(),
227− column: Column {
228− name: "name".to_string(),
229− data_type: "TEXT".to_string(),
230− nullable: false,
231− default: Some("'DEFAULT'".to_string()),
232− generated: None,
233− },
234− up: None,
235− });
236−
237− let first_migrations = vec![create_users_table.clone()];
238− let second_migrations = vec![create_users_table.clone(), add_name_column.clone()];
239−
240− // Run first migration, should automatically finish
241− reshape.migrate(first_migrations.clone()).unwrap();
242−
243− // Update search paths
244− old_db
245− .simple_query(&reshape::schema_query_for_migration(
246− &first_migrations.last().unwrap().name,
247− ))
248− .unwrap();
249− new_db
250− .simple_query(&reshape::schema_query_for_migration(
251− &first_migrations.last().unwrap().name,
252− ))
253− .unwrap();
254
217
255− // Insert some test values
218+ test.second_migration(
256− new_db
219+ r#"
257− .simple_query("INSERT INTO users (id) VALUES (1), (2)")
220+ name = "add_name_column_with_default"
258− .unwrap();
259
221
260− // Run second migration
222+ [[actions]]
261− reshape.migrate(second_migrations.clone()).unwrap();
223+ type = "add_column"
262− new_db
224+ table = "users"
263− .simple_query(&reshape::schema_query_for_migration(
264− &second_migrations.last().unwrap().name,
265− ))
266− .unwrap();
267
225
226+ [actions.column]
227+ name = "name"
228+ type = "TEXT"
229+ nullable = false
230+ default = "'DEFAULT'"
231+ "#,
232+ );
233+
234+ test.after_first(|db| {
235+ // Insert some test values
236+ db.simple_query("INSERT INTO users (id) VALUES (1), (2)")
237+ .unwrap();
238+ });
239+
240+ test.intermediate(|old_db, new_db| {
268− // Ensure existing data got updated with defaults
241+ // Ensure existing data got updated with defaults
269− let expected = vec!["DEFAULT".to_string(), "DEFAULT".to_string()];
242+ let expected = vec!["DEFAULT".to_string(), "DEFAULT".to_string()];
270− assert!(new_db
243+ assert!(new_db
271− .query("SELECT name FROM users ORDER BY id", &[],)
244+ .query("SELECT name FROM users ORDER BY id", &[],)
272− .unwrap()
245+ .unwrap()
273− .iter()
246+ .iter()
274− .map(|row| row.get::<_, String>("name"))
247+ .map(|row| row.get::<_, String>("name"))
275− .eq(expected));
248+ .eq(expected));
276−
249+
277− // Insert data using old schema and ensure new column gets the default value
250+ // Insert data using old schema and ensure new column gets the default value
278− old_db
251+ old_db
279− .simple_query("INSERT INTO users (id) VALUES (3)")
252+ .simple_query("INSERT INTO users (id) VALUES (3)")
280− .unwrap();
253+ .unwrap();
281− let name: String = new_db
254+ let name: String = new_db
282− .query_one("SELECT name from users WHERE id = 3", &[])
255+ .query_one("SELECT name from users WHERE id = 3", &[])
283− .map(|row| row.get("name"))
256+ .map(|row| row.get("name"))
284− .unwrap();
257+ .unwrap();
285− assert_eq!("DEFAULT", name);
258+ assert_eq!("DEFAULT", name);
259+ });
286
260
287− reshape.complete().unwrap();
261+ test.run();
288− common::assert_cleaned_up(&mut new_db);
289}
262}
tests/add_index.rs+194−202
@@ -1,231 +1,223 @@
1−use reshape::migrations::{AddIndex, ColumnBuilder, CreateTableBuilder, Index, Migration};
2−
3mod common;
1mod common;
2+use common::Test;
4
3
5#[test]
4#[test]
6fn add_index() {
5fn add_index() {
7− let (mut reshape, mut old_db, mut new_db) = common::setup();
6+ let mut test = Test::new("Add index");
8−
7+
9− let create_table_migration = Migration::new("create_user_table", None).with_action(
8+ test.first_migration(
10− CreateTableBuilder::default()
9+ r#"
10+ name = "create_users_table"
11+
12+ [[actions]]
13+ type = "create_table"
11− .name("users")
14+ name = "users"
12− .primary_key(vec!["id".to_string()])
15+ primary_key = ["id"]
16+
13− .columns(vec![
17+ [[actions.columns]]
14− ColumnBuilder::default()
15− .name("id")
18+ name = "id"
16− .data_type("INTEGER")
19+ type = "INTEGER"
17− .build()
20+
18− .unwrap(),
21+ [[actions.columns]]
19− ColumnBuilder::default()
20− .name("name")
22+ name = "name"
21− .data_type("TEXT")
23+ type = "TEXT"
22− .build()
24+ "#,
23− .unwrap(),
25+ );
24− ])
26+
25− .build()
27+ test.second_migration(
26− .unwrap(),
28+ r#"
29+ name = "add_users_name_index"
30+
31+ [[actions]]
32+ type = "add_index"
33+ table = "users"
34+
35+ [actions.index]
36+ name = "name_idx"
37+ columns = ["name"]
38+ "#,
27 );
39 );
28− let add_index_migration = Migration::new("add_name_index", None).with_action(AddIndex {
40+
29− table: "users".to_string(),
41+ test.intermediate(|db, _| {
30− index: Index {
42+ // Ensure index is valid and ready
31− name: "name_idx".to_string(),
43+ let (is_ready, is_valid): (bool, bool) = db
32− columns: vec!["name".to_string()],
44+ .query(
33− unique: true,
45+ "
34− index_type: None,
46+ SELECT pg_index.indisready, pg_index.indisvalid
35− },
47+ FROM pg_catalog.pg_index
48+ JOIN pg_catalog.pg_class ON pg_index.indexrelid = pg_class.oid
49+ WHERE pg_class.relname = 'name_idx'
50+ ",
51+ &[],
52+ )
53+ .unwrap()
54+ .first()
55+ .map(|row| (row.get("indisready"), row.get("indisvalid")))
56+ .unwrap();
57+
58+ assert!(is_ready, "expected index to be ready");
59+ assert!(is_valid, "expected index to be valid");
36 });
60 });
37
61
38− let first_migrations = vec![create_table_migration.clone()];
62+ test.after_completion(|db| {
39− let second_migrations = vec![create_table_migration.clone(), add_index_migration.clone()];
40−
41− // Run migrations
42− reshape.migrate(first_migrations.clone()).unwrap();
43− reshape.migrate(second_migrations.clone()).unwrap();
44−
45− // Update schemas of Postgres connections
46− let old_schema_query =
47− reshape::schema_query_for_migration(&first_migrations.last().unwrap().name);
48− let new_schema_query =
49− reshape::schema_query_for_migration(&second_migrations.last().unwrap().name);
50− old_db.simple_query(&old_schema_query).unwrap();
51− new_db.simple_query(&new_schema_query).unwrap();
52−
53− // Ensure index is valid and ready
63+ // Ensure index is valid and ready
54− let (is_ready, is_valid): (bool, bool) = new_db
64+ let (is_ready, is_valid): (bool, bool) = db
55− .query(
65+ .query(
56− "
66+ "
57− SELECT pg_index.indisready, pg_index.indisvalid
67+ SELECT pg_index.indisready, pg_index.indisvalid
58− FROM pg_catalog.pg_index
68+ FROM pg_catalog.pg_index
59− JOIN pg_catalog.pg_class ON pg_index.indexrelid = pg_class.oid
69+ JOIN pg_catalog.pg_class ON pg_index.indexrelid = pg_class.oid
60− WHERE pg_class.relname = 'name_idx'
70+ WHERE pg_class.relname = 'name_idx'
61− ",
71+ ",
62− &[],
72+ &[],
63− )
73+ )
64− .unwrap()
74+ .unwrap()
65− .first()
75+ .first()
66− .map(|row| (row.get("indisready"), row.get("indisvalid")))
76+ .map(|row| (row.get("indisready"), row.get("indisvalid")))
67− .unwrap();
77+ .unwrap();
68−
78+
69− assert!(is_ready, "expected index to be ready");
79+ assert!(is_ready, "expected index to be ready");
70− assert!(is_valid, "expected index to be valid");
80+ assert!(is_valid, "expected index to be valid");
71−
81+ });
72− reshape.complete().unwrap();
82+
73− common::assert_cleaned_up(&mut new_db);
83+ test.run();
74}
84}
75
85
76#[test]
86#[test]
77fn add_index_unique() {
87fn add_index_unique() {
78− let (mut reshape, mut old_db, mut new_db) = common::setup();
88+ let mut test = Test::new("Add unique index");
79−
89+
80− let create_table_migration = Migration::new("create_user_table", None).with_action(
90+ test.first_migration(
81− CreateTableBuilder::default()
91+ r#"
92+ name = "create_users_table"
93+
94+ [[actions]]
95+ type = "create_table"
82− .name("users")
96+ name = "users"
83− .primary_key(vec!["id".to_string()])
97+ primary_key = ["id"]
98+
84− .columns(vec![
99+ [[actions.columns]]
85− ColumnBuilder::default()
86− .name("id")
100+ name = "id"
87− .data_type("INTEGER")
101+ type = "INTEGER"
88− .build()
102+
89− .unwrap(),
103+ [[actions.columns]]
90− ColumnBuilder::default()
91− .name("name")
104+ name = "name"
92− .data_type("TEXT")
105+ type = "TEXT"
93− .build()
106+ "#,
94− .unwrap(),
107+ );
95− ])
108+
96− .build()
109+ test.second_migration(
97− .unwrap(),
110+ r#"
111+ name = "add_name_index"
112+
113+ [[actions]]
114+ type = "add_index"
115+ table = "users"
116+
117+ [actions.index]
118+ name = "name_idx"
119+ columns = ["name"]
120+ unique = true
121+ "#,
98 );
122 );
99− let add_index_migration = Migration::new("add_name_index", None).with_action(AddIndex {
100− table: "users".to_string(),
101− index: Index {
102− name: "name_idx".to_string(),
103− columns: vec!["name".to_string()],
104− unique: true,
105− index_type: None,
106− },
107− });
108
123
109− let first_migrations = vec![create_table_migration.clone()];
124+ test.intermediate(|db, _| {
110− let second_migrations = vec![create_table_migration.clone(), add_index_migration.clone()];
111−
112− // Run migrations
113− reshape.migrate(first_migrations.clone()).unwrap();
114− reshape.migrate(second_migrations.clone()).unwrap();
115−
116− // Update schemas of Postgres connections
117− let old_schema_query =
118− reshape::schema_query_for_migration(&first_migrations.last().unwrap().name);
119− let new_schema_query =
120− reshape::schema_query_for_migration(&second_migrations.last().unwrap().name);
121− old_db.simple_query(&old_schema_query).unwrap();
122− new_db.simple_query(&new_schema_query).unwrap();
123−
124− // Ensure index is valid, ready and unique
125+ // Ensure index is valid, ready and unique
125− let (is_ready, is_valid, is_unique): (bool, bool, bool) = new_db
126+ let (is_ready, is_valid, is_unique): (bool, bool, bool) = db
126− .query(
127+ .query(
127− "
128+ "
128 SELECT pg_index.indisready, pg_index.indisvalid, pg_index.indisunique
129 SELECT pg_index.indisready, pg_index.indisvalid, pg_index.indisunique
129 FROM pg_catalog.pg_index
130 FROM pg_catalog.pg_index
130 JOIN pg_catalog.pg_class ON pg_index.indexrelid = pg_class.oid
131 JOIN pg_catalog.pg_class ON pg_index.indexrelid = pg_class.oid
131 WHERE pg_class.relname = 'name_idx'
132 WHERE pg_class.relname = 'name_idx'
132 ",
133 ",
133− &[],
134+ &[],
134− )
135− .unwrap()
136− .first()
137− .map(|row| {
138− (
139− row.get("indisready"),
140− row.get("indisvalid"),
141− row.get("indisunique"),
142 )
135 )
136+ .unwrap()
137+ .first()
138+ .map(|row| {
139+ (
140+ row.get("indisready"),
141+ row.get("indisvalid"),
142+ row.get("indisunique"),
143+ )
143− })
144+ })
144− .unwrap();
145+ .unwrap();
145−
146+
146− assert!(is_ready, "expected index to be ready");
147+ assert!(is_ready, "expected index to be ready");
147− assert!(is_valid, "expected index to be valid");
148+ assert!(is_valid, "expected index to be valid");
148− assert!(is_unique, "expected index to be unique");
149+ assert!(is_unique, "expected index to be unique");
150+ });
149
151
150− reshape.complete().unwrap();
152+ test.run();
151− common::assert_cleaned_up(&mut new_db);
152}
153}
153
154
154#[test]
155#[test]
155fn add_index_with_type() {
156fn add_index_with_type() {
156− let (mut reshape, mut old_db, mut new_db) = common::setup();
157+ let mut test = Test::new("Add GIN index");
157−
158+
158− let create_table_migration = Migration::new("create_user_table", None).with_action(
159+ test.first_migration(
159− CreateTableBuilder::default()
160+ r#"
161+ name = "create_users_table"
162+
163+ [[actions]]
164+ type = "create_table"
160− .name("users")
165+ name = "users"
161− .primary_key(vec!["id".to_string()])
166+ primary_key = ["id"]
167+
162− .columns(vec![
168+ [[actions.columns]]
163− ColumnBuilder::default()
164− .name("id")
169+ name = "id"
165− .data_type("INTEGER")
170+ type = "INTEGER"
166− .build()
171+
167− .unwrap(),
172+ [[actions.columns]]
168− ColumnBuilder::default()
169− .name("data")
173+ name = "data"
170− .data_type("JSONB")
174+ type = "JSONB"
171− .build()
175+ "#,
172− .unwrap(),
173− ])
174− .build()
175− .unwrap(),
176 );
176 );
177− let add_index_migration = Migration::new("add_name_index", None).with_action(AddIndex {
178− table: "users".to_string(),
179− index: Index {
180− name: "name_idx".to_string(),
181− columns: vec!["data".to_string()],
182− unique: false,
183− index_type: Some("gin".to_string()),
184− },
185− });
186
177
187− let first_migrations = vec![create_table_migration.clone()];
178+ test.second_migration(
188− let second_migrations = vec![create_table_migration.clone(), add_index_migration.clone()];
179+ r#"
189−
180+ name = "add_data_index"
190− // Run migrations
191− reshape.migrate(first_migrations.clone()).unwrap();
192− reshape.migrate(second_migrations.clone()).unwrap();
193−
194− // Update schemas of Postgres connections
195− let old_schema_query =
196− reshape::schema_query_for_migration(&first_migrations.last().unwrap().name);
197− let new_schema_query =
198− reshape::schema_query_for_migration(&second_migrations.last().unwrap().name);
199− old_db.simple_query(&old_schema_query).unwrap();
200− new_db.simple_query(&new_schema_query).unwrap();
201−
202− // Ensure index is valid, ready and unique
203− let (is_ready, is_valid, index_type): (bool, bool, String) = new_db
204− .query(
205− "
206− SELECT pg_index.indisready, pg_index.indisvalid, pg_am.amname
207− FROM pg_catalog.pg_index
208− JOIN pg_catalog.pg_class ON pg_index.indexrelid = pg_class.oid
209− JOIN pg_catalog.pg_am ON pg_class.relam = pg_am.oid
210− WHERE pg_class.relname = 'name_idx'
211− ",
212− &[],
213− )
214− .unwrap()
215− .first()
216− .map(|row| {
217− (
218− row.get("indisready"),
219− row.get("indisvalid"),
220− row.get("amname"),
221− )
222− })
223− .unwrap();
224
181
182+ [[actions]]
183+ type = "add_index"
184+ table = "users"
185+
186+ [actions.index]
187+ name = "data_idx"
188+ columns = ["data"]
189+ type = "gin"
190+ "#,
191+ );
192+
193+ test.intermediate(|db, _| {
194+ // Ensure index is valid, ready and unique
195+ let (is_ready, is_valid, index_type): (bool, bool, String) = db
196+ .query(
197+ "
198+ SELECT pg_index.indisready, pg_index.indisvalid, pg_am.amname
199+ FROM pg_catalog.pg_index
200+ JOIN pg_catalog.pg_class ON pg_index.indexrelid = pg_class.oid
201+ JOIN pg_catalog.pg_am ON pg_class.relam = pg_am.oid
202+ WHERE pg_class.relname = 'data_idx'
203+ ",
204+ &[],
205+ )
206+ .unwrap()
207+ .first()
208+ .map(|row| {
209+ (
210+ row.get("indisready"),
211+ row.get("indisvalid"),
212+ row.get("amname"),
213+ )
214+ })
215+ .unwrap();
216+
225− assert!(is_ready, "expected index to be ready");
217+ assert!(is_ready, "expected index to be ready");
226− assert!(is_valid, "expected index to be valid");
218+ assert!(is_valid, "expected index to be valid");
227− assert_eq!("gin", index_type, "expected index type to be GIN");
219+ assert_eq!("gin", index_type, "expected index type to be GIN");
220+ });
228
221
229− reshape.complete().unwrap();
222+ test.run();
230− common::assert_cleaned_up(&mut new_db);
231}
223}
tests/alter_column.rs+382−501
@@ -1,61 +1,45 @@
1−use reshape::migrations::{
2− AddIndex, AlterColumn, ColumnBuilder, ColumnChanges, CreateTableBuilder, Index, Migration,
3−};
4−
5mod common;
1mod common;
2+use common::Test;
6
3
7#[test]
4#[test]
8fn alter_column_data() {
5fn alter_column_data() {
9− let (mut reshape, mut old_db, mut new_db) = common::setup();
6+ let mut test = Test::new("Alter column");
10−
11− let create_users_table = Migration::new("create_user_table", None).with_action(
12− CreateTableBuilder::default()
13− .name("users")
14− .primary_key(vec!["id".to_string()])
15− .columns(vec![
16− ColumnBuilder::default()
17− .name("id")
18− .data_type("INTEGER")
19− .build()
20− .unwrap(),
21− ColumnBuilder::default()
22− .name("name")
23− .data_type("TEXT")
24− .build()
25− .unwrap(),
26− ])
27− .build()
28− .unwrap(),
29− );
30− let uppercase_name = Migration::new("uppercase_name", None).with_action(AlterColumn {
31− table: "users".to_string(),
32− column: "name".to_string(),
33− up: Some("UPPER(name)".to_string()),
34− down: Some("LOWER(name)".to_string()),
35− changes: ColumnChanges {
36− data_type: None,
37− nullable: None,
38− name: None,
39− default: None,
40− },
41− });
42
7
43− let first_migrations = vec![create_users_table.clone()];
8+ test.first_migration(
44− let second_migrations = vec![create_users_table.clone(), uppercase_name.clone()];
9+ r#"
10+ name = "create_user_table"
45
11
46− // Run first migration, should automatically finish
12+ [[actions]]
47− reshape.migrate(first_migrations.clone()).unwrap();
13+ type = "create_table"
14+ name = "users"
15+ primary_key = ["id"]
48
16
49− // Update search paths
17+ [[actions.columns]]
50− old_db
18+ name = "id"
51− .simple_query(&reshape::schema_query_for_migration(
19+ type = "INTEGER"
52− &first_migrations.last().unwrap().name,
20+
53− ))
21+ [[actions.columns]]
54− .unwrap();
22+ name = "name"
23+ type = "TEXT"
24+ "#,
25+ );
55
26
27+ test.second_migration(
28+ r#"
29+ name = "uppercase_name"
30+
31+ [[actions]]
32+ type = "alter_column"
33+ table = "users"
34+ column = "name"
35+ up = "UPPER(name)"
36+ down = "LOWER(name)"
37+ "#,
38+ );
39+
40+ test.after_first(|db| {
56− // Insert some test users
41+ // Insert some test users
57− old_db
58− .simple_query(
42+ db.simple_query(
59 "
43 "
60 INSERT INTO users (id, name) VALUES
44 INSERT INTO users (id, name) VALUES
61 (1, 'john Doe'),
45 (1, 'john Doe'),
@@ -63,98 +47,82 @@ fn alter_column_data() {
63 ",
47 ",
64 )
48 )
65 .unwrap();
49 .unwrap();
50+ });
66
51
67− // Run second migration
52+ test.intermediate(|old_db, new_db| {
68− reshape.migrate(second_migrations.clone()).unwrap();
69− new_db
70− .simple_query(&reshape::schema_query_for_migration(
71− &second_migrations.last().unwrap().name,
72− ))
73− .unwrap();
74−
75− // Check that the existing users has the altered data
53+ // Check that the existing users has the altered data
76− let expected = vec!["JOHN DOE", "JANE DOE"];
54+ let expected = vec!["JOHN DOE", "JANE DOE"];
77− assert!(new_db
55+ assert!(new_db
78− .query("SELECT name FROM users ORDER BY id", &[],)
56+ .query("SELECT name FROM users ORDER BY id", &[],)
79− .unwrap()
57+ .unwrap()
80− .iter()
58+ .iter()
81− .map(|row| row.get::<_, String>("name"))
59+ .map(|row| row.get::<_, String>("name"))
82− .eq(expected));
60+ .eq(expected));
83−
61+
84− // Insert data using old schema and make sure the new schema gets correct values
62+ // Insert data using old schema and make sure the new schema gets correct values
85− old_db
63+ old_db
86− .simple_query("INSERT INTO users (id, name) VALUES (3, 'test testsson')")
64+ .simple_query("INSERT INTO users (id, name) VALUES (3, 'test testsson')")
87− .unwrap();
65+ .unwrap();
88− let result = new_db
66+ let result = new_db
89− .query_one("SELECT name from users WHERE id = 3", &[])
67+ .query_one("SELECT name from users WHERE id = 3", &[])
90− .unwrap();
68+ .unwrap();
91− assert_eq!("TEST TESTSSON", result.get::<_, &str>("name"));
69+ assert_eq!("TEST TESTSSON", result.get::<_, &str>("name"));
92−
70+
93− // Insert data using new schema and make sure the old schema gets correct values
71+ // Insert data using new schema and make sure the old schema gets correct values
94− new_db
72+ new_db
95− .simple_query("INSERT INTO users (id, name) VALUES (4, 'TEST TESTSSON')")
73+ .simple_query("INSERT INTO users (id, name) VALUES (4, 'TEST TESTSSON')")
96− .unwrap();
74+ .unwrap();
97− let result = old_db
75+ let result = old_db
98− .query_one("SELECT name from users WHERE id = 4", &[])
76+ .query_one("SELECT name from users WHERE id = 4", &[])
99− .unwrap();
77+ .unwrap();
100− assert_eq!("test testsson", result.get::<_, &str>("name"));
78+ assert_eq!("test testsson", result.get::<_, &str>("name"));
79+ });
101
80
102− reshape.complete().unwrap();
81+ test.run();
103− common::assert_cleaned_up(&mut new_db);
104}
82}
105
83
106#[test]
84#[test]
107fn alter_column_set_not_null() {
85fn alter_column_set_not_null() {
108− let (mut reshape, mut old_db, mut new_db) = common::setup();
86+ let mut test = Test::new("Set column not null");
109−
87+
110− let create_users_table = Migration::new("create_user_table", None).with_action(
88+ test.first_migration(
111− CreateTableBuilder::default()
89+ r#"
90+ name = "create_user_table"
91+
92+ [[actions]]
93+ type = "create_table"
112− .name("users")
94+ name = "users"
113− .primary_key(vec!["id".to_string()])
95+ primary_key = ["id"]
96+
114− .columns(vec![
97+ [[actions.columns]]
115− ColumnBuilder::default()
116− .name("id")
98+ name = "id"
117− .data_type("INTEGER")
99+ type = "INTEGER"
118− .build()
100+
119− .unwrap(),
101+ [[actions.columns]]
120− ColumnBuilder::default()
121− .name("name")
102+ name = "name"
122− .data_type("TEXT")
103+ type = "TEXT"
123− .build()
104+ "#,
124− .unwrap(),
125− ])
126− .build()
127− .unwrap(),
128 );
105 );
129− let set_name_not_null = Migration::new("set_name_not_null", None).with_action(AlterColumn {
130− table: "users".to_string(),
131− column: "name".to_string(),
132− up: Some("COALESCE(name, 'TEST_DEFAULT_VALUE')".to_string()),
133− down: Some("name".to_string()),
134− changes: ColumnChanges {
135− data_type: None,
136− nullable: Some(false),
137− name: None,
138− default: None,
139− },
140− });
141
106
142− let first_migrations = vec![create_users_table.clone()];
107+ test.second_migration(
143− let second_migrations = vec![create_users_table.clone(), set_name_not_null.clone()];
108+ r#"
109+ name = "set_name_not_null"
144
110
145− // Run first migration, should automatically finish
111+ [[actions]]
146− reshape.migrate(first_migrations.clone()).unwrap();
112+ type = "alter_column"
113+ table = "users"
114+ column = "name"
115+ up = "COALESCE(name, 'TEST_DEFAULT_VALUE')"
116+ down = "name"
147
117
148− // Update search paths
118+ [actions.changes]
149− old_db
119+ nullable = false
150− .simple_query(&reshape::schema_query_for_migration(
120+ "#,
151− &first_migrations.last().unwrap().name,
121+ );
152− ))
153− .unwrap();
154
122
123+ test.after_first(|db| {
155− // Insert some test users
124+ // Insert some test users
156− old_db
157− .simple_query(
125+ db.simple_query(
158 "
126 "
159 INSERT INTO users (id, name) VALUES
127 INSERT INTO users (id, name) VALUES
160 (1, 'John Doe'),
128 (1, 'John Doe'),
@@ -162,99 +130,80 @@ fn alter_column_set_not_null() {
162 ",
130 ",
163 )
131 )
164 .unwrap();
132 .unwrap();
133+ });
165
134
166− // Run second migration
135+ test.intermediate(|old_db, new_db| {
167− reshape.migrate(second_migrations.clone()).unwrap();
168− new_db
169− .simple_query(&reshape::schema_query_for_migration(
170− &second_migrations.last().unwrap().name,
171− ))
172− .unwrap();
173−
174− // Check that existing users got the correct values
136+ // Check that existing users got the correct values
175− let expected = vec!["John Doe", "TEST_DEFAULT_VALUE"];
137+ let expected = vec!["John Doe", "TEST_DEFAULT_VALUE"];
176− assert!(new_db
138+ assert!(new_db
177− .query("SELECT name FROM users ORDER BY id", &[],)
139+ .query("SELECT name FROM users ORDER BY id", &[],)
178− .unwrap()
140+ .unwrap()
179− .iter()
141+ .iter()
180− .map(|row| row.get::<_, String>("name"))
142+ .map(|row| row.get::<_, String>("name"))
181− .eq(expected));
143+ .eq(expected));
182−
144+
183− // Insert data using old schema and make sure the new schema gets correct values
145+ // Insert data using old schema and make sure the new schema gets correct values
184− old_db
146+ old_db
185− .simple_query("INSERT INTO users (id, name) VALUES (3, NULL)")
147+ .simple_query("INSERT INTO users (id, name) VALUES (3, NULL)")
186− .unwrap();
148+ .unwrap();
187− let result = new_db
149+ let result = new_db
188− .query_one("SELECT name from users WHERE id = 3", &[])
150+ .query_one("SELECT name from users WHERE id = 3", &[])
189− .unwrap();
151+ .unwrap();
190− assert_eq!("TEST_DEFAULT_VALUE", result.get::<_, &str>("name"));
152+ assert_eq!("TEST_DEFAULT_VALUE", result.get::<_, &str>("name"));
191−
153+
192− // Insert data using new schema and make sure the old schema gets correct values
154+ // Insert data using new schema and make sure the old schema gets correct values
193− new_db
155+ new_db
194− .simple_query("INSERT INTO users (id, name) VALUES (4, 'Jane Doe')")
156+ .simple_query("INSERT INTO users (id, name) VALUES (4, 'Jane Doe')")
195− .unwrap();
157+ .unwrap();
196− let result = old_db
158+ let result = old_db
197− .query_one("SELECT name from users WHERE id = 4", &[])
159+ .query_one("SELECT name from users WHERE id = 4", &[])
198− .unwrap();
160+ .unwrap();
199− assert_eq!("Jane Doe", result.get::<_, &str>("name"));
161+ assert_eq!("Jane Doe", result.get::<_, &str>("name"));
162+ });
200
163
201− reshape.complete().unwrap();
164+ test.run();
202− common::assert_cleaned_up(&mut new_db);
203}
165}
204
166
205#[test]
167#[test]
206fn alter_column_rename() {
168fn alter_column_rename() {
207− let (mut reshape, mut old_db, mut new_db) = common::setup();
169+ let mut test = Test::new("Rename column");
208−
170+
209− let create_users_table = Migration::new("create_user_table", None).with_action(
171+ test.first_migration(
210− CreateTableBuilder::default()
172+ r#"
173+ name = "create_user_table"
174+
175+ [[actions]]
176+ type = "create_table"
211− .name("users")
177+ name = "users"
212− .primary_key(vec!["id".to_string()])
178+ primary_key = ["id"]
179+
213− .columns(vec![
180+ [[actions.columns]]
214− ColumnBuilder::default()
215− .name("id")
181+ name = "id"
216− .data_type("INTEGER")
182+ type = "INTEGER"
217− .build()
183+
218− .unwrap(),
184+ [[actions.columns]]
219− ColumnBuilder::default()
220− .name("name")
185+ name = "name"
221− .data_type("TEXT")
186+ type = "TEXT"
222− .build()
187+ "#,
223− .unwrap(),
188+ );
224− ])
189+
225− .build()
190+ test.second_migration(
226− .unwrap(),
191+ r#"
192+ name = "set_name_not_null"
193+
194+ [[actions]]
195+ type = "alter_column"
196+ table = "users"
197+ column = "name"
198+
199+ [actions.changes]
200+ name = "full_name"
201+ "#,
227 );
202 );
228− let rename_to_full_name =
229− Migration::new("rename_to_full_name", None).with_action(AlterColumn {
230− table: "users".to_string(),
231− column: "name".to_string(),
232− up: None, // up and down are not required when only renaming a column
233− down: None,
234− changes: ColumnChanges {
235− data_type: None,
236− nullable: None,
237− name: Some("full_name".to_string()),
238− default: None,
239− },
240− });
241−
242− let first_migrations = vec![create_users_table.clone()];
243− let second_migrations = vec![create_users_table.clone(), rename_to_full_name.clone()];
244−
245− // Run first migration, should automatically finish
246− reshape.migrate(first_migrations.clone()).unwrap();
247−
248− // Update search paths
249− old_db
250− .simple_query(&reshape::schema_query_for_migration(
251− &first_migrations.last().unwrap().name,
252− ))
253− .unwrap();
254
203
204+ test.after_first(|db| {
255− // Insert some test data
205+ // Insert some test data
256− old_db
257− .simple_query(
206+ db.simple_query(
258 "
207 "
259 INSERT INTO users (id, name) VALUES
208 INSERT INTO users (id, name) VALUES
260 (1, 'John Doe'),
209 (1, 'John Doe'),
@@ -262,94 +211,69 @@ fn alter_column_rename() {
262 ",
211 ",
263 )
212 )
264 .unwrap();
213 .unwrap();
214+ });
265
215
266− // Run second migration
216+ test.intermediate(|_, new_db| {
267− reshape.migrate(second_migrations.clone()).unwrap();
217+ // Check that existing values can be queried using new column name
268− new_db
218+ let expected = vec!["John Doe", "Jane Doe"];
269− .simple_query(&reshape::schema_query_for_migration(
219+ assert!(new_db
270− &second_migrations.last().unwrap().name,
220+ .query("SELECT full_name FROM users ORDER BY id", &[],)
271− ))
221+ .unwrap()
272− .unwrap();
222+ .iter()
223+ .map(|row| row.get::<_, String>("full_name"))
224+ .eq(expected));
225+ });
273
226
274− // Check that existing values can be queried using new column name
227+ test.run();
275− let expected = vec!["John Doe", "Jane Doe"];
276− assert!(new_db
277− .query("SELECT full_name FROM users ORDER BY id", &[],)
278− .unwrap()
279− .iter()
280− .map(|row| row.get::<_, String>("full_name"))
281− .eq(expected));
282−
283− reshape.complete().unwrap();
284− common::assert_cleaned_up(&mut new_db);
285}
228}
286
229
287#[test]
230#[test]
288fn alter_column_multiple() {
231fn alter_column_multiple() {
289− let (mut reshape, mut old_db, mut new_db) = common::setup();
232+ let mut test = Test::new("Alter column value multiple times");
290−
233+
291− let create_users_table = Migration::new("create_user_table", None).with_action(
234+ test.first_migration(
292− CreateTableBuilder::default()
235+ r#"
236+ name = "create_user_table"
237+
238+ [[actions]]
239+ type = "create_table"
293− .name("users")
240+ name = "users"
294− .primary_key(vec!["id".to_string()])
241+ primary_key = ["id"]
242+
295− .columns(vec![
243+ [[actions.columns]]
296− ColumnBuilder::default()
297− .name("id")
244+ name = "id"
298− .data_type("INTEGER")
245+ type = "INTEGER"
299− .build()
246+
300− .unwrap(),
247+ [[actions.columns]]
301− ColumnBuilder::default()
302− .name("counter")
248+ name = "counter"
303− .data_type("INTEGER")
249+ type = "INTEGER"
304− .nullable(false)
250+ nullable = false
305− .build()
251+ "#,
306− .unwrap(),
252+ );
307− ])
253+
308− .build()
254+ test.second_migration(
309− .unwrap(),
255+ r#"
256+ name = "increment_counter_twice"
257+
258+ [[actions]]
259+ type = "alter_column"
260+ table = "users"
261+ column = "counter"
262+ up = "counter + 1"
263+ down = "counter - 1"
264+
265+ [[actions]]
266+ type = "alter_column"
267+ table = "users"
268+ column = "counter"
269+ up = "counter + 1"
270+ down = "counter - 1"
271+ "#,
310 );
272 );
311− let increment_counter_twice = Migration::new("increment_counter_twice", None)
312− .with_action(AlterColumn {
313− table: "users".to_string(),
314− column: "counter".to_string(),
315− up: Some("counter + 1".to_string()),
316− down: Some("counter - 1".to_string()),
317− changes: ColumnChanges {
318− data_type: None,
319− nullable: None,
320− name: None,
321− default: None,
322− },
323− })
324− .with_action(AlterColumn {
325− table: "users".to_string(),
326− column: "counter".to_string(),
327− up: Some("counter + 1".to_string()),
328− down: Some("counter - 1".to_string()),
329− changes: ColumnChanges {
330− data_type: None,
331− nullable: None,
332− name: None,
333− default: None,
334− },
335− });
336−
337− let first_migrations = vec![create_users_table.clone()];
338− let second_migrations = vec![create_users_table.clone(), increment_counter_twice.clone()];
339−
340− // Run first migration, should automatically finish
341− reshape.migrate(first_migrations.clone()).unwrap();
342−
343− // Update search paths
344− old_db
345− .simple_query(&reshape::schema_query_for_migration(
346− &first_migrations.last().unwrap().name,
347− ))
348− .unwrap();
349
273
274+ test.after_first(|db| {
350− // Insert some test data
275+ // Insert some test data
351− old_db
352− .simple_query(
276+ db.simple_query(
353 "
277 "
354 INSERT INTO users (id, counter) VALUES
278 INSERT INTO users (id, counter) VALUES
355 (1, 0),
279 (1, 0),
@@ -357,239 +281,196 @@ fn alter_column_multiple() {
357 ",
281 ",
358 )
282 )
359 .unwrap();
283 .unwrap();
284+ });
360
285
361− // Run second migration
286+ test.intermediate(|old_db, new_db| {
362− reshape.migrate(second_migrations.clone()).unwrap();
363− new_db
364− .simple_query(&reshape::schema_query_for_migration(
365− &second_migrations.last().unwrap().name,
366− ))
367− .unwrap();
368−
369− // Check that the existing data has been updated
287+ // Check that the existing data has been updated
370− let expected = vec![2, 102];
288+ let expected = vec![2, 102];
371− let results: Vec<i32> = new_db
289+ let results: Vec<i32> = new_db
372− .query("SELECT counter FROM users ORDER BY id", &[])
290+ .query("SELECT counter FROM users ORDER BY id", &[])
373− .unwrap()
291+ .unwrap()
374− .iter()
292+ .iter()
375− .map(|row| row.get::<_, i32>("counter"))
293+ .map(|row| row.get::<_, i32>("counter"))
376− .collect();
294+ .collect();
377− assert_eq!(expected, results);
295+ assert_eq!(expected, results);
378−
296+
379− // Update data using old schema and make sure it was updated for the new schema
297+ // Update data using old schema and make sure it was updated for the new schema
380− old_db
298+ old_db
381− .query("UPDATE users SET counter = 50 WHERE id = 1", &[])
299+ .query("UPDATE users SET counter = 50 WHERE id = 1", &[])
382− .unwrap();
300+ .unwrap();
383− let result: i32 = new_db
301+ let result: i32 = new_db
384− .query("SELECT counter FROM users WHERE id = 1", &[])
302+ .query("SELECT counter FROM users WHERE id = 1", &[])
385− .unwrap()
303+ .unwrap()
386− .iter()
304+ .iter()
387− .map(|row| row.get("counter"))
305+ .map(|row| row.get("counter"))
388− .nth(0)
306+ .nth(0)
389− .unwrap();
307+ .unwrap();
390− assert_eq!(52, result);
308+ assert_eq!(52, result);
391−
309+
392− // Update data using new schema and make sure it was updated for the old schema
310+ // Update data using new schema and make sure it was updated for the old schema
393− new_db
311+ new_db
394− .query("UPDATE users SET counter = 50 WHERE id = 1", &[])
312+ .query("UPDATE users SET counter = 50 WHERE id = 1", &[])
395− .unwrap();
313+ .unwrap();
396− let result: i32 = old_db
314+ let result: i32 = old_db
397− .query("SELECT counter FROM users WHERE id = 1", &[])
315+ .query("SELECT counter FROM users WHERE id = 1", &[])
398− .unwrap()
316+ .unwrap()
399− .iter()
317+ .iter()
400− .map(|row| row.get("counter"))
318+ .map(|row| row.get("counter"))
401− .nth(0)
319+ .nth(0)
402− .unwrap();
320+ .unwrap();
403− assert_eq!(48, result);
321+ assert_eq!(48, result);
322+ });
404
323
405− reshape.complete().unwrap();
324+ test.run();
406− common::assert_cleaned_up(&mut new_db);
407}
325}
408
326
409#[test]
327#[test]
410fn alter_column_default() {
328fn alter_column_default() {
411− let (mut reshape, mut old_db, mut new_db) = common::setup();
329+ let mut test = Test::new("Change default value for column");
412−
330+
413− let create_users_table = Migration::new("create_user_table", None).with_action(
331+ test.first_migration(
414− CreateTableBuilder::default()
332+ r#"
333+ name = "create_user_table"
334+
335+ [[actions]]
336+ type = "create_table"
415− .name("users")
337+ name = "users"
416− .primary_key(vec!["id".to_string()])
338+ primary_key = ["id"]
339+
417− .columns(vec![
340+ [[actions.columns]]
418− ColumnBuilder::default()
419− .name("id")
341+ name = "id"
420− .data_type("INTEGER")
342+ type = "INTEGER"
421− .build()
343+
422− .unwrap(),
344+ [[actions.columns]]
423− ColumnBuilder::default()
424− .name("name")
345+ name = "name"
425− .data_type("TEXT")
346+ type = "TEXT"
426− .nullable(false)
347+ nullable = false
427− .default_value("'DEFAULT'")
348+ default = "'DEFAULT'"
428− .build()
349+ "#,
429− .unwrap(),
430− ])
431− .build()
432− .unwrap(),
433 );
350 );
434− let change_name_default =
435− Migration::new("change_name_default", None).with_action(AlterColumn {
436− table: "users".to_string(),
437− column: "name".to_string(),
438− up: None,
439− down: None,
440− changes: ColumnChanges {
441− data_type: None,
442− nullable: None,
443− name: None,
444− default: Some("'NEW DEFAULT'".to_string()),
445− },
446− });
447−
448− let first_migrations = vec![create_users_table.clone()];
449− let second_migrations = vec![create_users_table.clone(), change_name_default.clone()];
450−
451− // Run first migration, should automatically finish
452− reshape.migrate(first_migrations.clone()).unwrap();
453−
454− // Update search paths
455− old_db
456− .simple_query(&reshape::schema_query_for_migration(
457− &first_migrations.last().unwrap().name,
458− ))
459− .unwrap();
460
351
352+ test.second_migration(
353+ r#"
354+ name = "change_name_default"
355+
356+ [[actions]]
357+ type = "alter_column"
358+ table = "users"
359+ column = "name"
360+
361+ [actions.changes]
362+ default = "'NEW DEFAULT'"
363+ "#,
364+ );
365+
366+ test.after_first(|db| {
461− // Insert a test user
367+ // Insert a test user
462− old_db
463− .simple_query(
368+ db.simple_query(
464 "
369 "
465 INSERT INTO users (id) VALUES (1)
370 INSERT INTO users (id) VALUES (1)
466 ",
371 ",
467 )
372 )
468 .unwrap();
373 .unwrap();
374+ });
469
375
470− // Run second migration
376+ test.intermediate(|old_db, new_db| {
471− reshape.migrate(second_migrations.clone()).unwrap();
472− new_db
473− .simple_query(&reshape::schema_query_for_migration(
474− &second_migrations.last().unwrap().name,
475− ))
476− .unwrap();
477−
478− // Check that the existing users has the old default value
377+ // Check that the existing users has the old default value
479− let expected = vec!["DEFAULT"];
378+ let expected = vec!["DEFAULT"];
480− assert!(new_db
379+ assert!(new_db
481− .query("SELECT name FROM users", &[],)
380+ .query("SELECT name FROM users", &[],)
482− .unwrap()
381+ .unwrap()
483− .iter()
382+ .iter()
484− .map(|row| row.get::<_, String>("name"))
383+ .map(|row| row.get::<_, String>("name"))
485− .eq(expected));
384+ .eq(expected));
486−
385+
487− // Insert data using old schema and make those get the old default value
386+ // Insert data using old schema and make those get the old default value
488− old_db
387+ old_db
489− .simple_query("INSERT INTO users (id) VALUES (2)")
388+ .simple_query("INSERT INTO users (id) VALUES (2)")
490− .unwrap();
389+ .unwrap();
491− let result = new_db
390+ let result = new_db
492− .query_one("SELECT name from users WHERE id = 2", &[])
391+ .query_one("SELECT name from users WHERE id = 2", &[])
493− .unwrap();
392+ .unwrap();
494− assert_eq!("DEFAULT", result.get::<_, &str>("name"));
393+ assert_eq!("DEFAULT", result.get::<_, &str>("name"));
495−
394+
496− // Insert data using new schema and make sure it gets the new default value
395+ // Insert data using new schema and make sure it gets the new default value
497− new_db
396+ new_db
498− .simple_query("INSERT INTO users (id) VALUES (3)")
397+ .simple_query("INSERT INTO users (id) VALUES (3)")
499− .unwrap();
398+ .unwrap();
500− let result = old_db
399+ let result = old_db
501− .query_one("SELECT name from users WHERE id = 3", &[])
400+ .query_one("SELECT name from users WHERE id = 3", &[])
502− .unwrap();
401+ .unwrap();
503− assert_eq!("NEW DEFAULT", result.get::<_, &str>("name"));
402+ assert_eq!("NEW DEFAULT", result.get::<_, &str>("name"));
403+ });
504
404
505− reshape.complete().unwrap();
405+ test.run();
506− common::assert_cleaned_up(&mut new_db);
507}
406}
508
407
509#[test]
408#[test]
510fn alter_column_with_index() {
409fn alter_column_with_index() {
511− let (mut reshape, mut db, _) = common::setup();
410+ let mut test = Test::new("Alter column with index");
512−
513− let create_users_table = Migration::new("create_user_table", None)
514− .with_action(
515− CreateTableBuilder::default()
516− .name("users")
517− .primary_key(vec!["id".to_string()])
518− .columns(vec![
519− ColumnBuilder::default()
520− .name("id")
521− .data_type("INTEGER")
522− .build()
523− .unwrap(),
524− ColumnBuilder::default()
525− .name("first_name")
526− .data_type("TEXT")
527− .build()
528− .unwrap(),
529− ColumnBuilder::default()
530− .name("last_name")
531− .data_type("TEXT")
532− .build()
533− .unwrap(),
534− ])
535− .build()
536− .unwrap(),
537− )
538− .with_action(AddIndex {
539− table: "users".to_string(),
540− index: Index {
541− name: "users_name_idx".to_string(),
542− columns: vec!["first_name".to_string(), "last_name".to_string()],
543− unique: false,
544− index_type: None,
545− },
546− });
547− let uppercase_name = Migration::new("uppercase_name", None).with_action(AlterColumn {
548− table: "users".to_string(),
549− column: "last_name".to_string(),
550− up: Some("UPPER(last_name)".to_string()),
551− down: Some("LOWER(last_name)".to_string()),
552− changes: ColumnChanges {
553− data_type: None,
554− nullable: None,
555− name: None,
556− default: None,
557− },
558− });
559
411
560− let first_migrations = vec![create_users_table.clone()];
412+ test.first_migration(
561− let second_migrations = vec![create_users_table.clone(), uppercase_name.clone()];
413+ r#"
414+ name = "create_user_table"
562
415
563− // Run first migration, should automatically finish
416+ [[actions]]
564− reshape.migrate(first_migrations.clone()).unwrap();
417+ type = "create_table"
418+ name = "users"
419+ primary_key = ["id"]
565
420
566− // Run second migration
421+ [[actions.columns]]
567− reshape.migrate(second_migrations.clone()).unwrap();
422+ name = "id"
568− db.simple_query(&reshape::schema_query_for_migration(
423+ type = "INTEGER"
569− &second_migrations.last().unwrap().name,
570− ))
571− .unwrap();
572
424
573− // Complete the second migration which should replace the existing column
425+ [[actions.columns]]
574− // with the temporary one
426+ name = "first_name"
575− reshape.complete().unwrap();
427+ type = "TEXT"
576
428
429+ [[actions.columns]]
430+ name = "last_name"
431+ type = "TEXT"
432+
433+ [[actions]]
434+ type = "add_index"
435+ table = "users"
436+
437+ [actions.index]
438+ name = "users_name_idx"
439+ columns = ["first_name", "last_name"]
440+ "#,
441+ );
442+
443+ test.second_migration(
444+ r#"
445+ name = "uppercase_last_name"
446+
447+ [[actions]]
448+ type = "alter_column"
449+ table = "users"
450+ column = "last_name"
451+ up = "UPPER(last_name)"
452+ down = "LOWER(last_name)"
453+ "#,
454+ );
455+
456+ test.after_completion(|db| {
577− // Make sure index still exists
457+ // Make sure index still exists
578− let result: i64 = db
458+ let result: i64 = db
579− .query(
459+ .query(
580− "
460+ "
581 SELECT COUNT(*)
461 SELECT COUNT(*)
582 FROM pg_catalog.pg_index
462 FROM pg_catalog.pg_index
583 JOIN pg_catalog.pg_class ON pg_index.indexrelid = pg_class.oid
463 JOIN pg_catalog.pg_class ON pg_index.indexrelid = pg_class.oid
584 WHERE pg_class.relname = 'users_name_idx'
464 WHERE pg_class.relname = 'users_name_idx'
585 ",
465 ",
586− &[],
466+ &[],
587− )
467+ )
588− .unwrap()
468+ .unwrap()
589− .first()
469+ .first()
590− .map(|row| row.get(0))
470+ .map(|row| row.get(0))
591− .unwrap();
471+ .unwrap();
592− assert_eq!(1, result, "expected index to still exist");
472+ assert_eq!(1, result, "expected index to still exist");
473+ });
593
474
594− common::assert_cleaned_up(&mut db);
475+ test.run();
595}
476}
tests/common.rs+29−18
@@ -10,6 +10,7 @@ pub struct Test<'a> {
10
10
11 first_migration: Option<Migration>,
11 first_migration: Option<Migration>,
12 second_migration: Option<Migration>,
12 second_migration: Option<Migration>,
13+ expect_failure: bool,
13
14
14 after_first_fn: Option<fn(&mut Client) -> ()>,
15 after_first_fn: Option<fn(&mut Client) -> ()>,
15 intermediate_fn: Option<fn(&mut Client, &mut Client) -> ()>,
16 intermediate_fn: Option<fn(&mut Client, &mut Client) -> ()>,
@@ -34,6 +35,7 @@ impl Test<'_> {
34 new_db,
35 new_db,
35 first_migration: None,
36 first_migration: None,
36 second_migration: None,
37 second_migration: None,
38+ expect_failure: false,
37 after_first_fn: None,
39 after_first_fn: None,
38 intermediate_fn: None,
40 intermediate_fn: None,
39 after_completion_fn: None,
41 after_completion_fn: None,
@@ -46,31 +48,41 @@ impl Test<'_> {
46 self
48 self
47 }
49 }
48
50
51+ #[allow(dead_code)]
49 pub fn second_migration(&mut self, migration: &str) -> &mut Self {
52 pub fn second_migration(&mut self, migration: &str) -> &mut Self {
50 self.second_migration = Some(Self::parse_migration(migration));
53 self.second_migration = Some(Self::parse_migration(migration));
51 self
54 self
52 }
55 }
53
56
57+ #[allow(dead_code)]
54 pub fn after_first(&mut self, f: fn(&mut Client) -> ()) -> &mut Self {
58 pub fn after_first(&mut self, f: fn(&mut Client) -> ()) -> &mut Self {
55 self.after_first_fn = Some(f);
59 self.after_first_fn = Some(f);
56 self
60 self
57 }
61 }
58
62
63+ #[allow(dead_code)]
59 pub fn intermediate(&mut self, f: fn(&mut Client, &mut Client) -> ()) -> &mut Self {
64 pub fn intermediate(&mut self, f: fn(&mut Client, &mut Client) -> ()) -> &mut Self {
60 self.intermediate_fn = Some(f);
65 self.intermediate_fn = Some(f);
61 self
66 self
62 }
67 }
63
68
69+ #[allow(dead_code)]
64 pub fn after_completion(&mut self, f: fn(&mut Client) -> ()) -> &mut Self {
70 pub fn after_completion(&mut self, f: fn(&mut Client) -> ()) -> &mut Self {
65 self.after_completion_fn = Some(f);
71 self.after_completion_fn = Some(f);
66 self
72 self
67 }
73 }
68
74
75+ #[allow(dead_code)]
69 pub fn after_abort(&mut self, f: fn(&mut Client) -> ()) -> &mut Self {
76 pub fn after_abort(&mut self, f: fn(&mut Client) -> ()) -> &mut Self {
70 self.after_abort_fn = Some(f);
77 self.after_abort_fn = Some(f);
71 self
78 self
72 }
79 }
73
80
81+ #[allow(dead_code)]
82+ pub fn expect_failure(&mut self) {
83+ self.expect_failure = true;
84+ }
85+
74 fn parse_migration(encoded: &str) -> Migration {
86 fn parse_migration(encoded: &str) -> Migration {
75 toml::from_str(encoded).unwrap()
87 toml::from_str(encoded).unwrap()
76 }
88 }
@@ -83,6 +95,7 @@ enum RunType {
83}
95}
84
96
85impl Test<'_> {
97impl Test<'_> {
98+ #[allow(dead_code)]
86 pub fn run(&mut self) {
99 pub fn run(&mut self) {
87 if self.second_migration.is_some() {
100 if self.second_migration.is_some() {
88 // Run to completion
101 // Run to completion
@@ -124,10 +137,21 @@ impl Test<'_> {
124
137
125 // Apply second migration
138 // Apply second migration
126 if let Some(second_migration) = &self.second_migration {
139 if let Some(second_migration) = &self.second_migration {
140+ if self.expect_failure {
127− print_subheading("Applying second migration");
141+ print_subheading("Applying second migration (expecting failure)");
142+ let result = self
143+ .reshape
144+ .migrate(vec![first_migration.clone(), second_migration.clone()]);
145+
146+ if result.is_ok() {
147+ panic!("expected second migration to fail");
148+ }
149+ } else {
150+ print_subheading("Applying second migration");
128− self.reshape
151+ self.reshape
129− .migrate(vec![first_migration.clone(), second_migration.clone()])
152+ .migrate(vec![first_migration.clone(), second_migration.clone()])
130− .unwrap();
153+ .unwrap();
154+ }
131
155
132 // Update search path
156 // Update search path
133 self.new_db
157 self.new_db
@@ -191,7 +215,7 @@ fn print_success() {
191}
215}
192
216
193fn add_spacer(text: &str, char: &str) -> String {
217fn add_spacer(text: &str, char: &str) -> String {
194− const TARGET_WIDTH: usize = 50;
218+ const TARGET_WIDTH: usize = 80;
195 let num_of_chars = (TARGET_WIDTH - text.len() - 2) / 2;
219 let num_of_chars = (TARGET_WIDTH - text.len() - 2) / 2;
196 let spacer = std::iter::repeat(char)
220 let spacer = std::iter::repeat(char)
197 .take(num_of_chars)
221 .take(num_of_chars)
@@ -202,19 +226,6 @@ fn add_spacer(text: &str, char: &str) -> String {
202 format!("{spacer} {text} {spacer}{extra}", spacer = spacer)
226 format!("{spacer} {text} {spacer}{extra}", spacer = spacer)
203}
227}
204
228
205−pub fn setup() -> (Reshape, Client, Client) {
206− let connection_string = std::env::var("POSTGRES_CONNECTION_STRING")
207− .unwrap_or("postgres://postgres:postgres@localhost/reshape_test".to_string());
208−
209− let old_db = Client::connect(&connection_string, NoTls).unwrap();
210− let new_db = Client::connect(&connection_string, NoTls).unwrap();
211−
212− let mut reshape = Reshape::new(&connection_string).unwrap();
213− reshape.remove().unwrap();
214−
215− (reshape, old_db, new_db)
216−}
217−
218pub fn assert_cleaned_up(db: &mut Client) {
229pub fn assert_cleaned_up(db: &mut Client) {
219 // Make sure no temporary columns remain
230 // Make sure no temporary columns remain
220 let temp_columns: Vec<String> = db
231 let temp_columns: Vec<String> = db
tests/create_table.rs+67−69
@@ -1,10 +1,9 @@
1−use reshape::migrations::{ColumnBuilder, CreateTableBuilder, ForeignKey, Migration};
2−
3mod common;
1mod common;
2+use common::Test;
4
3
5#[test]
4#[test]
6fn create_table() {
5fn create_table() {
7− let mut test = common::Test::new("Create table");
6+ let mut test = Test::new("Create table");
8
7
9 test.first_migration(
8 test.first_migration(
10 r#"
9 r#"
@@ -113,59 +112,57 @@ fn create_table() {
113
112
114#[test]
113#[test]
115fn create_table_with_foreign_keys() {
114fn create_table_with_foreign_keys() {
116− let (mut reshape, mut db, _) = common::setup();
115+ let mut test = Test::new("Create table");
117−
118− let create_table_migration = Migration::new("create_users_table", None).with_action(
119− CreateTableBuilder::default()
120− .name("users")
121− .primary_key(vec!["id".to_string()])
122− .columns(vec![ColumnBuilder::default()
123− .name("id")
124− .data_type("INTEGER")
125− .generated("ALWAYS AS IDENTITY")
126− .build()
127− .unwrap()])
128− .build()
129− .unwrap(),
130− );
131
116
132− let create_second_table_migration = Migration::new("create_items_table", None).with_action(
117+ test.first_migration(
133− CreateTableBuilder::default()
118+ r#"
119+ name = "create_users_table"
120+
121+ [[actions]]
122+ type = "create_table"
134− .name("items")
123+ name = "users"
135− .primary_key(vec!["id".to_string()])
124+ primary_key = ["id"]
136− .foreign_keys(vec![ForeignKey {
125+
137− columns: vec!["user_id".to_string()],
126+ [[actions.columns]]
138− referenced_table: "users".to_string(),
127+ name = "id"
139− referenced_columns: vec!["id".to_string()],
140− }])
141− .columns(vec![
142− ColumnBuilder::default()
143− .name("id")
144− .data_type("INTEGER")
128+ type = "INTEGER"
145− .generated("ALWAYS AS IDENTITY")
129+ generated = "ALWAYS AS IDENTITY"
146− .build()
130+
147− .unwrap(),
131+ [[actions.columns]]
148− ColumnBuilder::default()
132+ name = "name"
149− .name("user_id")
133+ type = "TEXT"
150− .data_type("INTEGER")
134+
135+ [[actions.columns]]
136+ name = "created_at"
137+ type = "TIMESTAMP"
151− .nullable(false)
138+ nullable = false
152− .build()
139+ default = "NOW()"
153− .unwrap(),
154− ])
155− .build()
156− .unwrap(),
157− );
158
140
159− reshape
141+ [[actions]]
160− .migrate(vec![
142+ type = "create_table"
161− create_table_migration.clone(),
143+ name = "items"
162− create_second_table_migration.clone(),
144+ primary_key = ["id"]
163− ])
145+
164− .unwrap();
146+ [[actions.columns]]
147+ name = "id"
148+ type = "INTEGER"
149+
150+ [[actions.columns]]
151+ name = "user_id"
152+ type = "INTEGER"
153+ nullable = false
154+
155+ [[actions.foreign_keys]]
156+ columns = ["user_id"]
157+ referenced_table = "users"
158+ referenced_columns = ["id"]
159+ "#,
160+ );
165
161
162+ test.after_first(|db| {
166− let foreign_key_columns: Vec<(String, String, String)> = db
163+ let foreign_key_columns: Vec<(String, String, String)> = db
167− .query(
164+ .query(
168− "
165+ "
169 SELECT
166 SELECT
170 kcu.column_name,
167 kcu.column_name,
171 ccu.table_name AS foreign_table_name,
168 ccu.table_name AS foreign_table_name,
@@ -180,23 +177,24 @@ fn create_table_with_foreign_keys() {
180 AND ccu.table_schema = tc.table_schema
177 AND ccu.table_schema = tc.table_schema
181 WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_name='items';
178 WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_name='items';
182 ",
179 ",
183− &[],
180+ &[],
184− )
185− .unwrap()
186− .iter()
187− .map(|row| {
188− (
189− row.get("column_name"),
190− row.get("foreign_table_name"),
191− row.get("foreign_column_name"),
192 )
181 )
182+ .unwrap()
183+ .iter()
184+ .map(|row| {
185+ (
186+ row.get("column_name"),
187+ row.get("foreign_table_name"),
188+ row.get("foreign_column_name"),
189+ )
193− })
190+ })
194− .collect();
191+ .collect();
195
192
196− assert_eq!(
193+ assert_eq!(
197− vec![("user_id".to_string(), "users".to_string(), "id".to_string())],
194+ vec![("user_id".to_string(), "users".to_string(), "id".to_string())],
198− foreign_key_columns
195+ foreign_key_columns
196+ );
199− );
197+ });
200
198
201− common::assert_cleaned_up(&mut db);
199+ test.run();
202}
200}
tests/failure.rs+34−58
@@ -1,75 +1,51 @@
1−use reshape::migrations::{AddColumn, Column, ColumnBuilder, CreateTableBuilder, Migration};
2−
3mod common;
1mod common;
2+use common::Test;
4
3
5#[test]
4#[test]
6fn invalid_migration() {
5fn invalid_migration() {
7− let (mut reshape, mut old_db, mut new_db) = common::setup();
6+ let mut test = Test::new("Invalid migration");
7+
8+ test.first_migration(
9+ r#"
10+ name = "invalid_migration"
11+
12+ [[actions]]
13+ type = "create_table"
14+ name = "users"
15+ primary_key = ["id"]
8
16
9− let create_users_table = Migration::new("create_users_table", None).with_action(
17+ [[actions.columns]]
10− CreateTableBuilder::default()
18+ name = "id"
11− .name("users")
19+ type = "INTEGER"
12− .primary_key(vec!["id".to_string()])
20+ "#,
13− .columns(vec![
14− ColumnBuilder::default()
15− .name("id")
16− .data_type("INTEGER")
17− .build()
18− .unwrap(),
19− ColumnBuilder::default()
20− .name("name")
21− .data_type("TEXT")
22− .build()
23− .unwrap(),
24− ])
25− .build()
26− .unwrap(),
27 );
21 );
28− let add_first_column = Migration::new("add_first_column", None).with_action(AddColumn {
29− table: "users".to_string(),
30− column: Column {
31− name: "first".to_string(),
32− data_type: "TEXT".to_string(),
33− nullable: false,
34− default: None,
35− generated: None,
36− },
37− up: Some("INVALID SQL".to_string()),
38− });
39
22
40− let first_migrations = vec![create_users_table.clone()];
23+ test.second_migration(
41− let second_migrations = vec![create_users_table.clone(), add_first_column.clone()];
24+ r#"
25+ name = "add_invalid_column"
42
26
43− // Run first migration, should automatically finish
27+ [[actions]]
44− reshape.migrate(first_migrations.clone()).unwrap();
28+ type = "add_column"
29+ table = "users"
45
30
46− // Update search paths
31+ up = "INVALID SQL"
47− old_db
32+
48− .simple_query(&reshape::schema_query_for_migration(
33+ [actions.column]
49− &first_migrations.last().unwrap().name,
34+ name = "first"
50− ))
35+ type = "TEXT"
51− .unwrap();
36+ "#,
52− new_db
37+ );
53− .simple_query(&reshape::schema_query_for_migration(
54− &first_migrations.last().unwrap().name,
55− ))
56− .unwrap();
57
38
58 // Insert a test user
39 // Insert a test user
59− new_db
40+ test.after_first(|db| {
60− .simple_query(
41+ db.simple_query(
61 "
42 "
62− INSERT INTO users (id, name) VALUES
43+ INSERT INTO users (id) VALUES (1)
63− (1, 'John Doe')
64 ",
44 ",
65 )
45 )
66 .unwrap();
46 .unwrap();
47+ });
67
48
68− // Run second migration and ensure it fails
49+ test.expect_failure();
69− assert!(
50+ test.run();
70− reshape.migrate(second_migrations.clone()).is_err(),
71− "invalid migration succeeded"
72− );
73−
74− common::assert_cleaned_up(&mut new_db);
75}
51}
tests/remove_column.rs+54−68
@@ -1,81 +1,67 @@
1−use reshape::migrations::{ColumnBuilder, CreateTableBuilder, Migration, RemoveColumn};
2−
3mod common;
1mod common;
2+use common::Test;
4
3
5#[test]
4#[test]
6fn remove_column() {
5fn remove_column() {
7− let (mut reshape, mut old_db, mut new_db) = common::setup();
6+ let mut test = Test::new("Remove column");
8−
9− let create_table_migration = Migration::new("create_user_table", None).with_action(
10− CreateTableBuilder::default()
11− .name("users")
12− .primary_key(vec!["id".to_string()])
13− .columns(vec![
14− ColumnBuilder::default()
15− .name("id")
16− .data_type("INTEGER")
17− .build()
18− .unwrap(),
19− ColumnBuilder::default()
20− .name("name")
21− .data_type("TEXT")
22− .nullable(false)
23− .build()
24− .unwrap(),
25− ])
26− .build()
27− .unwrap(),
28− );
29− let remove_column_migration =
30− Migration::new("remove_name_column", None).with_action(RemoveColumn {
31− table: "users".to_string(),
32− column: "name".to_string(),
33− down: Some("'TEST_DOWN_VALUE'".to_string()),
34− });
35
7
36− let first_migrations = vec![create_table_migration.clone()];
8+ test.first_migration(
37− let second_migrations = vec![
9+ r#"
38− create_table_migration.clone(),
10+ name = "create_user_table"
39− remove_column_migration.clone(),
40− ];
41
11
42− // Run migrations
12+ [[actions]]
43− reshape.migrate(first_migrations.clone()).unwrap();
13+ type = "create_table"
44− reshape.migrate(second_migrations.clone()).unwrap();
14+ name = "users"
15+ primary_key = ["id"]
45
16
46− // Update schemas of Postgres connections
17+ [[actions.columns]]
47− let old_schema_query =
18+ name = "id"
48− reshape::schema_query_for_migration(&first_migrations.last().unwrap().name);
19+ type = "INTEGER"
49− let new_schema_query =
50− reshape::schema_query_for_migration(&second_migrations.last().unwrap().name);
51− old_db.simple_query(&old_schema_query).unwrap();
52− new_db.simple_query(&new_schema_query).unwrap();
53
20
54− // Insert using old schema and ensure it can be retrieved through new schema
21+ [[actions.columns]]
55− old_db
22+ name = "name"
56− .simple_query("INSERT INTO users(id, name) VALUES (1, 'John Doe')")
23+ type = "TEXT"
57− .unwrap();
24+ "#,
58− let results = new_db
25+ );
59− .query("SELECT id FROM users WHERE id = 1", &[])
60− .unwrap();
61− assert_eq!(1, results.len());
62− assert_eq!(1, results[0].get::<_, i32>("id"));
63
26
64− // Ensure the name column is not accesible through the new schema
27+ test.second_migration(
65− assert!(new_db.query("SELECT id, name FROM users", &[]).is_err());
28+ r#"
29+ name = "remove_name_column"
66
30
67− // Insert using new schema and ensure the down function is correctly applied
31+ [[actions]]
68− new_db
32+ type = "remove_column"
69− .simple_query("INSERT INTO users(id) VALUES (2)")
33+ table = "users"
70− .unwrap();
34+ column = "name"
71− let result = old_db
72− .query_opt("SELECT name FROM users WHERE id = 2", &[])
73− .unwrap();
74− assert_eq!(
75− Some("TEST_DOWN_VALUE"),
35+ down = "'TEST_DOWN_VALUE'"
76− result.as_ref().map(|row| row.get("name"))
36+ "#,
77 );
37 );
78
38
39+ test.intermediate(|old_db, new_db| {
40+ // Insert using old schema and ensure it can be retrieved through new schema
41+ old_db
42+ .simple_query("INSERT INTO users(id, name) VALUES (1, 'John Doe')")
79− reshape.complete().unwrap();
43+ .unwrap();
80− common::assert_cleaned_up(&mut new_db);
44+ let results = new_db
45+ .query("SELECT id FROM users WHERE id = 1", &[])
46+ .unwrap();
47+ assert_eq!(1, results.len());
48+ assert_eq!(1, results[0].get::<_, i32>("id"));
49+
50+ // Ensure the name column is not accesible through the new schema
51+ assert!(new_db.query("SELECT id, name FROM users", &[]).is_err());
52+
53+ // Insert using new schema and ensure the down function is correctly applied
54+ new_db
55+ .simple_query("INSERT INTO users(id) VALUES (2)")
56+ .unwrap();
57+ let result = old_db
58+ .query_opt("SELECT name FROM users WHERE id = 2", &[])
59+ .unwrap();
60+ assert_eq!(
61+ Some("TEST_DOWN_VALUE"),
62+ result.as_ref().map(|row| row.get("name"))
63+ );
64+ });
65+
66+ test.run();
81}
67}
tests/remove_index.rs+68−97
@@ -1,115 +1,86 @@
1−use reshape::migrations::{
2− AddIndex, ColumnBuilder, CreateTableBuilder, Index, Migration, RemoveIndex,
3−};
4−
5mod common;
1mod common;
2+use common::Test;
6
3
7#[test]
4#[test]
8fn remove_index() {
5fn remove_index() {
9− let (mut reshape, _, mut db) = common::setup();
6+ let mut test = Test::new("Remove index");
10
7
11− let create_table_migration = Migration::new("create_user_table", None)
8+ test.first_migration(
12− .with_action(
9+ r#"
13− CreateTableBuilder::default()
10+ name = "create_users_table"
14− .name("users")
15− .primary_key(vec!["id".to_string()])
16− .columns(vec![
17− ColumnBuilder::default()
18− .name("id")
19− .data_type("INTEGER")
20− .build()
21− .unwrap(),
22− ColumnBuilder::default()
23− .name("name")
24− .data_type("TEXT")
25− .build()
26− .unwrap(),
27− ])
28− .build()
29− .unwrap(),
30− )
31− .with_action(AddIndex {
32− table: "users".to_string(),
33− index: Index {
34− name: "name_idx".to_string(),
35− columns: vec!["name".to_string()],
36− unique: false,
37− index_type: None,
38− },
39− });
40
11
41− let remove_index_migration =
12+ [[actions]]
42− Migration::new("remove_name_index", None).with_action(RemoveIndex {
13+ type = "create_table"
43− index: "name_idx".to_string(),
14+ name = "users"
44− });
15+ primary_key = ["id"]
45
16
46− let first_migrations = vec![create_table_migration.clone()];
17+ [[actions.columns]]
47− let second_migrations = vec![
18+ name = "id"
48− create_table_migration.clone(),
19+ type = "INTEGER"
49− remove_index_migration.clone(),
50− ];
51
20
52− // Run migrations
21+ [[actions.columns]]
53− reshape.migrate(first_migrations.clone()).unwrap();
22+ name = "name"
23+ type = "TEXT"
54
24
55− // Ensure index is valid and ready
25+ [[actions]]
56− let result: Vec<(bool, bool)> = db
26+ type = "add_index"
57− .query(
27+ table = "users"
58− "
59− SELECT pg_index.indisready, pg_index.indisvalid
60− FROM pg_catalog.pg_index
61− JOIN pg_catalog.pg_class ON pg_index.indexrelid = pg_class.oid
62− WHERE pg_class.relname = 'name_idx'
63− ",
64− &[],
65− )
66− .unwrap()
67− .iter()
68− .map(|row| (row.get("indisready"), row.get("indisvalid")))
69− .collect();
70
28
71− assert_eq!(vec![(true, true)], result);
29+ [actions.index]
30+ name = "name_idx"
31+ columns = ["name"]
32+ "#,
33+ );
72
34
73− // Run migration to remove index
35+ test.second_migration(
74− reshape.migrate(second_migrations.clone()).unwrap();
36+ r#"
37+ name = "remove_name_index"
75
38
76− // Ensure index is still valid and ready during the migration
39+ [[actions]]
77− let result: Vec<(bool, bool)> = db
40+ type = "remove_index"
78− .query(
41+ index = "name_idx"
79− "
42+ "#,
80− SELECT pg_index.indisready, pg_index.indisvalid
43+ );
81− FROM pg_catalog.pg_index
82− JOIN pg_catalog.pg_class ON pg_index.indexrelid = pg_class.oid
83− WHERE pg_class.relname = 'name_idx'
84− ",
85− &[],
86− )
87− .unwrap()
88− .iter()
89− .map(|row| (row.get("indisready"), row.get("indisvalid")))
90− .collect();
91
44
92− assert_eq!(vec![(true, true)], result);
45+ test.intermediate(|db, _| {
46+ // Ensure index is still valid and ready during the migration
47+ let result: Vec<(bool, bool)> = db
48+ .query(
49+ "
50+ SELECT pg_index.indisready, pg_index.indisvalid
51+ FROM pg_catalog.pg_index
52+ JOIN pg_catalog.pg_class ON pg_index.indexrelid = pg_class.oid
53+ WHERE pg_class.relname = 'name_idx'
54+ ",
55+ &[],
56+ )
57+ .unwrap()
58+ .iter()
59+ .map(|row| (row.get("indisready"), row.get("indisvalid")))
60+ .collect();
93
61
94− reshape.complete().unwrap();
62+ assert_eq!(vec![(true, true)], result);
63+ });
95
64
65+ test.after_completion(|db| {
96− // Ensure index has been removed after the migration is complete
66+ // Ensure index has been removed after the migration is complete
97− let count: i64 = db
67+ let count: i64 = db
98− .query(
68+ .query(
99− "
69+ "
100− SELECT COUNT(*)
70+ SELECT COUNT(*)
101− FROM pg_catalog.pg_index
71+ FROM pg_catalog.pg_index
102− JOIN pg_catalog.pg_class ON pg_index.indexrelid = pg_class.oid
72+ JOIN pg_catalog.pg_class ON pg_index.indexrelid = pg_class.oid
103− WHERE pg_class.relname = 'name_idx'
73+ WHERE pg_class.relname = 'name_idx'
104− ",
74+ ",
105− &[],
75+ &[],
106− )
76+ )
107− .unwrap()
77+ .unwrap()
108− .first()
78+ .first()
109− .map(|row| row.get(0))
79+ .map(|row| row.get(0))
110− .unwrap();
80+ .unwrap();
111
81
112− assert_eq!(0, count, "expected index to not exist");
82+ assert_eq!(0, count, "expected index to not exist");
83+ });
113
84
114− common::assert_cleaned_up(&mut db);
85+ test.run();
115}
86}
tests/remove_table.rs+32−44
@@ -1,54 +1,42 @@
1−use reshape::migrations::{ColumnBuilder, CreateTableBuilder, Migration, RemoveTable};
2−
3mod common;
1mod common;
2+use common::Test;
4
3
5#[test]
4#[test]
6fn remove_table() {
5fn remove_table() {
7− let (mut reshape, mut old_db, mut new_db) = common::setup();
6+ let mut test = Test::new("Remove table");
8−
7+
9− let create_table_migration = Migration::new("create_user_table", None).with_action(
8+ test.first_migration(
10− CreateTableBuilder::default()
9+ r#"
10+ name = "create_users_table"
11+
12+ [[actions]]
13+ type = "create_table"
11− .name("users")
14+ name = "users"
12− .primary_key(vec!["id".to_string()])
15+ primary_key = ["id"]
13− .columns(vec![ColumnBuilder::default()
16+
17+ [[actions.columns]]
14− .name("id")
18+ name = "id"
15− .data_type("INTEGER")
19+ type = "INTEGER"
16− .build()
20+ "#,
17− .unwrap()])
18− .build()
19− .unwrap(),
20 );
21 );
21− let remove_table_migration =
22− Migration::new("remove_users_table", None).with_action(RemoveTable {
23− table: "users".to_string(),
24− });
25−
26− let first_migrations = vec![create_table_migration.clone()];
27− let second_migrations = vec![
28− create_table_migration.clone(),
29− remove_table_migration.clone(),
30− ];
31
22
32− // Run migrations
23+ test.second_migration(
33− reshape.migrate(first_migrations.clone()).unwrap();
24+ r#"
34− reshape.migrate(second_migrations.clone()).unwrap();
25+ name = "remove_users_table"
35
26
36− // Update schemas of Postgres connections
27+ [[actions]]
37− let old_schema_query =
28+ type = "remove_table"
38− reshape::schema_query_for_migration(&first_migrations.last().unwrap().name);
29+ table = "users"
39− let new_schema_query =
30+ "#,
40− reshape::schema_query_for_migration(&second_migrations.last().unwrap().name);
31+ );
41− old_db.simple_query(&old_schema_query).unwrap();
42− new_db.simple_query(&new_schema_query).unwrap();
43−
44− // Make sure inserts work against the old schema
45− old_db
46− .simple_query("INSERT INTO users(id) VALUES (1)")
47− .unwrap();
48
32
49− // Ensure the table is not accessible through the new schema
33+ test.intermediate(|old_db, new_db| {
50− assert!(new_db.query("SELECT id FROM users", &[]).is_err());
34+ // Make sure inserts work against the old schema
35+ old_db
36+ .simple_query("INSERT INTO users(id) VALUES (1)")
37+ .unwrap();
51
38
52− reshape.complete().unwrap();
39+ // Ensure the table is not accessible through the new schema
53− common::assert_cleaned_up(&mut new_db);
40+ assert!(new_db.query("SELECT id FROM users", &[]).is_err());
41+ });
54}
42}
tests/rename_table.rs+56−66
@@ -1,80 +1,70 @@
1−use reshape::migrations::{ColumnBuilder, CreateTableBuilder, Migration, RenameTable};
2−
3mod common;
1mod common;
2+use common::Test;
4
3
5#[test]
4#[test]
6fn rename_table() {
5fn rename_table() {
7− let (mut reshape, mut old_db, mut new_db) = common::setup();
6+ let mut test = Test::new("Rename table");
8−
9− let create_table_migration = Migration::new("create_user_table", None).with_action(
10− CreateTableBuilder::default()
11− .name("users")
12− .primary_key(vec!["id".to_string()])
13− .columns(vec![ColumnBuilder::default()
14− .name("id")
15− .data_type("INTEGER")
16− .build()
17− .unwrap()])
18− .build()
19− .unwrap(),
20− );
21− let rename_table_migration = Migration::new("rename_users_table_to_customers", None)
22− .with_action(RenameTable {
23− table: "users".to_string(),
24− new_name: "customers".to_string(),
25− });
26
7
27− let first_migrations = vec![create_table_migration.clone()];
8+ test.first_migration(
28− let second_migrations = vec![
9+ r#"
29− create_table_migration.clone(),
10+ name = "create_users_table"
30− rename_table_migration.clone(),
11+
31− ];
12+ [[actions]]
13+ type = "create_table"
14+ name = "users"
15+ primary_key = ["id"]
32
16
33− // Run migrations
17+ [[actions.columns]]
34− reshape.migrate(first_migrations.clone()).unwrap();
18+ name = "id"
35− reshape.migrate(second_migrations.clone()).unwrap();
19+ type = "INTEGER"
20+ "#,
21+ );
36
22
37− // Update schemas of Postgres connections
23+ test.second_migration(
38− let old_schema_query =
24+ r#"
39− reshape::schema_query_for_migration(&first_migrations.last().unwrap().name);
25+ name = "rename_users_table_to_customers"
40− let new_schema_query =
41− reshape::schema_query_for_migration(&second_migrations.last().unwrap().name);
42− old_db.simple_query(&old_schema_query).unwrap();
43− new_db.simple_query(&new_schema_query).unwrap();
44
26
45− // Make sure inserts work using both the old and new name
27+ [[actions]]
46− old_db
28+ type = "rename_table"
47− .simple_query("INSERT INTO users(id) VALUES (1)")
29+ table = "users"
48− .unwrap();
30+ new_name = "customers"
49− new_db
31+ "#,
50− .simple_query("INSERT INTO customers(id) VALUES (2)")
32+ );
51− .unwrap();
52
33
34+ test.intermediate(|old_db, new_db| {
53− // Ensure the table can be queried using both the old and new name
35+ // Make sure inserts work using both the old and new name
54− let expected: Vec<i32> = vec![1, 2];
55− assert_eq!(
56− expected,
57 old_db
36 old_db
58− .query("SELECT id FROM users ORDER BY id", &[])
37+ .simple_query("INSERT INTO users(id) VALUES (1)")
59− .unwrap()
38+ .unwrap();
60− .iter()
61− .map(|row| row.get::<_, i32>("id"))
62− .collect::<Vec<i32>>()
63− );
64− assert_eq!(
65− expected,
66 new_db
39 new_db
40+ .simple_query("INSERT INTO customers(id) VALUES (2)")
41+ .unwrap();
42+
43+ // Ensure the table can be queried using both the old and new name
44+ let expected: Vec<i32> = vec![1, 2];
45+ assert_eq!(
46+ expected,
47+ old_db
67− .query("SELECT id FROM customers ORDER BY id", &[])
48+ .query("SELECT id FROM users ORDER BY id", &[])
68− .unwrap()
49+ .unwrap()
50+ .iter()
51+ .map(|row| row.get::<_, i32>("id"))
52+ .collect::<Vec<i32>>()
53+ );
54+ assert_eq!(
55+ expected,
56+ new_db
57+ .query("SELECT id FROM customers ORDER BY id", &[])
58+ .unwrap()
69− .iter()
59+ .iter()
70− .map(|row| row.get::<_, i32>("id"))
60+ .map(|row| row.get::<_, i32>("id"))
71− .collect::<Vec<i32>>()
61+ .collect::<Vec<i32>>()
72− );
62+ );
73
63
74− // Ensure the table can't be queried using the wrong name for the schema
64+ // Ensure the table can't be queried using the wrong name for the schema
75− assert!(old_db.simple_query("SELECT id FROM customers").is_err());
65+ assert!(old_db.simple_query("SELECT id FROM customers").is_err());
76− assert!(new_db.simple_query("SELECT id FROM users").is_err());
66+ assert!(new_db.simple_query("SELECT id FROM users").is_err());
67+ });
77
68
78− reshape.complete().unwrap();
69+ test.run();
79− common::assert_cleaned_up(&mut new_db);
80}
70}