on main help sign in
Refactor tests to use test framework

This has the added benefit of removing the need for builders and the derive_builder dependency.

commit9cac25299a114519b58397ea48dccfe286ee14b6 authorfabianlindfors <fabian@flapplabs.se> dateSat 15 Jan 2022, 16:15 (5 years ago) parent35a52eb
14 files changed+1047−1294
Cargo.toml+1−2 src/migrations/common.rs+1−10 src/migrations/create_table.rs+1−4 src/migrations/mod.rs+2−2 tests/add_column.rs+126−153 tests/add_index.rs+194−202 tests/alter_column.rs+382−501 tests/common.rs+29−18 tests/create_table.rs+67−69 tests/failure.rs+34−58 tests/remove_column.rs+54−68 tests/remove_index.rs+68−97 tests/remove_table.rs+32−44 tests/rename_table.rs+56−66
Cargo.toml+1−2
@@ -19,5 +19,4 @@ anyhow = { version = "1.0.44", features = ["backtrace"] }
1919clap = { version = "3.0.0", features = ["derive"] }
2020toml = "0.5"
2121version = "3.0.0"
22−colored = "2"
23−"derive_builder" = "0.10.2"
22+colored = "2"
src/migrations/common.rs+1−10
@@ -1,26 +1,17 @@
11use anyhow::anyhow;
2−use derive_builder::Builder;
32use postgres::types::{FromSql, ToSql};
43use serde::{Deserialize, Serialize};
54
65use crate::db::Conn;
76
8−#[derive(Serialize, Deserialize, Builder, Clone, Debug)]
9−#[builder(setter(into))]
7+#[derive(Serialize, Deserialize, Clone, Debug)]
108pub struct Column {
119 pub name: String,
12−
1310 #[serde(rename = "type")]
1411 pub data_type: String,
15−
1612 #[serde(default = "nullable_default")]
17− #[builder(default = "true")]
1813 pub nullable: bool,
19−
20− #[builder(setter(name = "default_value", strip_option), default)]
2114 pub default: Option<String>,
22−
23− #[builder(setter(strip_option), default)]
2415 pub generated: Option<String>,
2516}
2617
src/migrations/create_table.rs+1−4
@@ -4,18 +4,15 @@ use crate::{
44 schema::Schema,
55};
66use anyhow::Context;
7−use derive_builder::Builder;
87use serde::{Deserialize, Serialize};
98
10−#[derive(Serialize, Deserialize, Builder, Debug)]
11−#[builder(setter(into))]
9+#[derive(Serialize, Deserialize, Debug)]
1210pub struct CreateTable {
1311 pub name: String,
1412 pub columns: Vec<Column>,
1513 pub primary_key: Vec<String>,
1614
1715 #[serde(default)]
18− #[builder(default)]
1916 pub foreign_keys: Vec<ForeignKey>,
2017}
2118
src/migrations/mod.rs+2−2
@@ -7,10 +7,10 @@ use serde::{Deserialize, Serialize};
77
88// Re-export migration types
99mod common;
10−pub use common::{Column, ColumnBuilder};
10+pub use common::Column;
1111
1212mod create_table;
13−pub use create_table::{CreateTable, CreateTableBuilder, ForeignKey};
13+pub use create_table::{CreateTable, ForeignKey};
1414
1515mod alter_column;
1616pub 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−
31mod common;
2+use common::Test;
43
54#[test]
65fn add_column() {
7− let mut test = common::Test::new("Add column");
6+ let mut test = Test::new("Add column");
87
98 test.first_migration(
109 r#"
@@ -114,176 +113,150 @@ fn add_column() {
114113
115114#[test]
116115fn add_column_nullable() {
117− let (mut reshape, mut old_db, mut new_db) = common::setup();
118−
119− let create_users_table = Migration::new("create_user_table", None).with_action(
120− CreateTableBuilder::default()
121− .name("users")
122− .primary_key(vec!["id".to_string()])
123− .columns(vec![ColumnBuilder::default()
124− .name("id")
125− .data_type("INTEGER")
126− .build()
127− .unwrap()])
128− .build()
129− .unwrap(),
116+ let mut test = Test::new("Add nullable column");
117+
118+ test.first_migration(
119+ r#"
120+ name = "create_users_table"
121+
122+ [[actions]]
123+ type = "create_table"
124+ name = "users"
125+ primary_key = ["id"]
126+
127+ [[actions.columns]]
128+ name = "id"
129+ type = "INTEGER"
130+ "#,
130131 );
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− });
142132
143− let first_migrations = vec![create_users_table.clone()];
144− let second_migrations = vec![create_users_table.clone(), add_name_column.clone()];
133+ test.second_migration(
134+ r#"
135+ name = "add_nullable_name_column"
145136
146− // Run first migration, should automatically finish
147− reshape.migrate(first_migrations.clone()).unwrap();
137+ [[actions]]
138+ type = "add_column"
139+ table = "users"
148140
149− // Update search paths
150− old_db
151− .simple_query(&reshape::schema_query_for_migration(
152− &first_migrations.last().unwrap().name,
153− ))
154− .unwrap();
155− new_db
156− .simple_query(&reshape::schema_query_for_migration(
157− &first_migrations.last().unwrap().name,
158− ))
159− .unwrap();
141+ [actions.column]
142+ name = "name"
143+ type = "TEXT"
144+ "#,
145+ );
160146
161− // Insert some test values
162− new_db
163− .simple_query(
147+ test.after_first(|db| {
148+ // Insert some test values
149+ db.simple_query(
164150 "
165− INSERT INTO users (id) VALUES
166− (1),
167− (2);
151+ INSERT INTO users (id) VALUES (1), (2);
168152 ",
169153 )
170154 .unwrap();
155+ });
171156
172− // Run second migration
173− reshape.migrate(second_migrations.clone()).unwrap();
174− new_db
175− .simple_query(&reshape::schema_query_for_migration(
176− &second_migrations.last().unwrap().name,
177− ))
178− .unwrap();
157+ test.intermediate(|old_db, new_db| {
158+ // Ensure existing data got updated
159+ let expected: Vec<Option<String>> = vec![None, None];
160+ assert!(new_db
161+ .query("SELECT name FROM users ORDER BY id", &[],)
162+ .unwrap()
163+ .iter()
164+ .map(|row| row.get::<_, Option<String>>("name"))
165+ .eq(expected));
179166
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
190− old_db
191− .simple_query("INSERT INTO users (id) VALUES (3)")
192− .unwrap();
193− let name: Option<String> = new_db
194− .query_one("SELECT name from users WHERE id = 3", &[])
195− .map(|row| (row.get("name")))
196− .unwrap();
197− assert_eq!(None, name);
167+ // Insert data using old schema and ensure new column is NULL
168+ old_db
169+ .simple_query("INSERT INTO users (id) VALUES (3)")
170+ .unwrap();
171+ let name: Option<String> = new_db
172+ .query_one("SELECT name from users WHERE id = 3", &[])
173+ .map(|row| (row.get("name")))
174+ .unwrap();
175+ assert_eq!(None, name);
198176
199− // Ensure data can be inserted against new schema
200− new_db
201− .simple_query("INSERT INTO users (id, name) VALUES (4, 'Test Testsson'), (5, NULL)")
202− .unwrap();
177+ // Ensure data can be inserted against new schema
178+ new_db
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", &[])
188+ .unwrap()
189+ .iter()
190+ .map(|row| row.get("name"))
191+ .collect();
203192
204− reshape.complete().unwrap();
205− common::assert_cleaned_up(&mut new_db);
193+ assert_eq!(result, expected);
194+ });
195+
196+ test.run();
206197}
207198
208199#[test]
209200fn add_column_with_default() {
210− let (mut reshape, mut old_db, mut new_db) = common::setup();
211−
212− let create_users_table = Migration::new("create_user_table", None).with_action(
213− CreateTableBuilder::default()
214− .name("users")
215− .primary_key(vec!["id".to_string()])
216− .columns(vec![ColumnBuilder::default()
217− .name("id")
218− .data_type("INTEGER")
219− .build()
220− .unwrap()])
221− .build()
222− .unwrap(),
201+ let mut test = Test::new("Add column with default value");
202+
203+ test.first_migration(
204+ r#"
205+ name = "create_users_table"
206+
207+ [[actions]]
208+ type = "create_table"
209+ name = "users"
210+ primary_key = ["id"]
211+
212+ [[actions.columns]]
213+ name = "id"
214+ type = "INTEGER"
215+ "#,
223216 );
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();
254217
255− // Insert some test values
256− new_db
257− .simple_query("INSERT INTO users (id) VALUES (1), (2)")
258− .unwrap();
218+ test.second_migration(
219+ r#"
220+ name = "add_name_column_with_default"
259221
260− // Run second migration
261− reshape.migrate(second_migrations.clone()).unwrap();
262− new_db
263− .simple_query(&reshape::schema_query_for_migration(
264− &second_migrations.last().unwrap().name,
265− ))
266− .unwrap();
222+ [[actions]]
223+ type = "add_column"
224+ table = "users"
267225
268− // Ensure existing data got updated with defaults
269− let expected = vec!["DEFAULT".to_string(), "DEFAULT".to_string()];
270− assert!(new_db
271− .query("SELECT name FROM users ORDER BY id", &[],)
272− .unwrap()
273− .iter()
274− .map(|row| row.get::<_, String>("name"))
275− .eq(expected));
276−
277− // Insert data using old schema and ensure new column gets the default value
278− old_db
279− .simple_query("INSERT INTO users (id) VALUES (3)")
280− .unwrap();
281− let name: String = new_db
282− .query_one("SELECT name from users WHERE id = 3", &[])
283− .map(|row| row.get("name"))
284− .unwrap();
285− assert_eq!("DEFAULT", name);
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| {
241+ // Ensure existing data got updated with defaults
242+ let expected = vec!["DEFAULT".to_string(), "DEFAULT".to_string()];
243+ assert!(new_db
244+ .query("SELECT name FROM users ORDER BY id", &[],)
245+ .unwrap()
246+ .iter()
247+ .map(|row| row.get::<_, String>("name"))
248+ .eq(expected));
249+
250+ // Insert data using old schema and ensure new column gets the default value
251+ old_db
252+ .simple_query("INSERT INTO users (id) VALUES (3)")
253+ .unwrap();
254+ let name: String = new_db
255+ .query_one("SELECT name from users WHERE id = 3", &[])
256+ .map(|row| row.get("name"))
257+ .unwrap();
258+ assert_eq!("DEFAULT", name);
259+ });
286260
287− reshape.complete().unwrap();
288− common::assert_cleaned_up(&mut new_db);
261+ test.run();
289262}
tests/add_index.rs+194−202
@@ -1,231 +1,223 @@
1−use reshape::migrations::{AddIndex, ColumnBuilder, CreateTableBuilder, Index, Migration};
2−
31mod common;
2+use common::Test;
43
54#[test]
65fn add_index() {
7− let (mut reshape, mut old_db, mut new_db) = common::setup();
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− .build()
23− .unwrap(),
24− ])
25− .build()
26− .unwrap(),
6+ let mut test = Test::new("Add index");
7+
8+ test.first_migration(
9+ r#"
10+ name = "create_users_table"
11+
12+ [[actions]]
13+ type = "create_table"
14+ name = "users"
15+ primary_key = ["id"]
16+
17+ [[actions.columns]]
18+ name = "id"
19+ type = "INTEGER"
20+
21+ [[actions.columns]]
22+ name = "name"
23+ type = "TEXT"
24+ "#,
25+ );
26+
27+ test.second_migration(
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+ "#,
2739 );
28− let add_index_migration = Migration::new("add_name_index", None).with_action(AddIndex {
29− table: "users".to_string(),
30− index: Index {
31− name: "name_idx".to_string(),
32− columns: vec!["name".to_string()],
33− unique: true,
34− index_type: None,
35− },
40+
41+ test.intermediate(|db, _| {
42+ // Ensure index is valid and ready
43+ let (is_ready, is_valid): (bool, bool) = db
44+ .query(
45+ "
46+ SELECT pg_index.indisready, pg_index.indisvalid
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");
3660 });
3761
38− let first_migrations = vec![create_table_migration.clone()];
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
54− let (is_ready, is_valid): (bool, bool) = new_db
55− .query(
56− "
57− SELECT pg_index.indisready, pg_index.indisvalid
58− FROM pg_catalog.pg_index
59− JOIN pg_catalog.pg_class ON pg_index.indexrelid = pg_class.oid
60− WHERE pg_class.relname = 'name_idx'
61− ",
62− &[],
63− )
64− .unwrap()
65− .first()
66− .map(|row| (row.get("indisready"), row.get("indisvalid")))
67− .unwrap();
68−
69− assert!(is_ready, "expected index to be ready");
70− assert!(is_valid, "expected index to be valid");
71−
72− reshape.complete().unwrap();
73− common::assert_cleaned_up(&mut new_db);
62+ test.after_completion(|db| {
63+ // Ensure index is valid and ready
64+ let (is_ready, is_valid): (bool, bool) = db
65+ .query(
66+ "
67+ SELECT pg_index.indisready, pg_index.indisvalid
68+ FROM pg_catalog.pg_index
69+ JOIN pg_catalog.pg_class ON pg_index.indexrelid = pg_class.oid
70+ WHERE pg_class.relname = 'name_idx'
71+ ",
72+ &[],
73+ )
74+ .unwrap()
75+ .first()
76+ .map(|row| (row.get("indisready"), row.get("indisvalid")))
77+ .unwrap();
78+
79+ assert!(is_ready, "expected index to be ready");
80+ assert!(is_valid, "expected index to be valid");
81+ });
82+
83+ test.run();
7484}
7585
7686#[test]
7787fn add_index_unique() {
78− let (mut reshape, mut old_db, mut new_db) = common::setup();
79−
80− let create_table_migration = Migration::new("create_user_table", None).with_action(
81− CreateTableBuilder::default()
82− .name("users")
83− .primary_key(vec!["id".to_string()])
84− .columns(vec![
85− ColumnBuilder::default()
86− .name("id")
87− .data_type("INTEGER")
88− .build()
89− .unwrap(),
90− ColumnBuilder::default()
91− .name("name")
92− .data_type("TEXT")
93− .build()
94− .unwrap(),
95− ])
96− .build()
97− .unwrap(),
88+ let mut test = Test::new("Add unique index");
89+
90+ test.first_migration(
91+ r#"
92+ name = "create_users_table"
93+
94+ [[actions]]
95+ type = "create_table"
96+ name = "users"
97+ primary_key = ["id"]
98+
99+ [[actions.columns]]
100+ name = "id"
101+ type = "INTEGER"
102+
103+ [[actions.columns]]
104+ name = "name"
105+ type = "TEXT"
106+ "#,
107+ );
108+
109+ test.second_migration(
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+ "#,
98122 );
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− });
108123
109− let first_migrations = vec![create_table_migration.clone()];
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− let (is_ready, is_valid, is_unique): (bool, bool, bool) = new_db
126− .query(
127− "
124+ test.intermediate(|db, _| {
125+ // Ensure index is valid, ready and unique
126+ let (is_ready, is_valid, is_unique): (bool, bool, bool) = db
127+ .query(
128+ "
128129 SELECT pg_index.indisready, pg_index.indisvalid, pg_index.indisunique
129130 FROM pg_catalog.pg_index
130131 JOIN pg_catalog.pg_class ON pg_index.indexrelid = pg_class.oid
131132 WHERE pg_class.relname = 'name_idx'
132133 ",
133− &[],
134− )
135− .unwrap()
136− .first()
137− .map(|row| {
138− (
139− row.get("indisready"),
140− row.get("indisvalid"),
141− row.get("indisunique"),
134+ &[],
142135 )
143− })
144− .unwrap();
145−
146− assert!(is_ready, "expected index to be ready");
147− assert!(is_valid, "expected index to be valid");
148− assert!(is_unique, "expected index to be unique");
136+ .unwrap()
137+ .first()
138+ .map(|row| {
139+ (
140+ row.get("indisready"),
141+ row.get("indisvalid"),
142+ row.get("indisunique"),
143+ )
144+ })
145+ .unwrap();
146+
147+ assert!(is_ready, "expected index to be ready");
148+ assert!(is_valid, "expected index to be valid");
149+ assert!(is_unique, "expected index to be unique");
150+ });
149151
150− reshape.complete().unwrap();
151− common::assert_cleaned_up(&mut new_db);
152+ test.run();
152153}
153154
154155#[test]
155156fn add_index_with_type() {
156− let (mut reshape, mut old_db, mut new_db) = common::setup();
157−
158− let create_table_migration = Migration::new("create_user_table", None).with_action(
159− CreateTableBuilder::default()
160− .name("users")
161− .primary_key(vec!["id".to_string()])
162− .columns(vec![
163− ColumnBuilder::default()
164− .name("id")
165− .data_type("INTEGER")
166− .build()
167− .unwrap(),
168− ColumnBuilder::default()
169− .name("data")
170− .data_type("JSONB")
171− .build()
172− .unwrap(),
173− ])
174− .build()
175− .unwrap(),
157+ let mut test = Test::new("Add GIN index");
158+
159+ test.first_migration(
160+ r#"
161+ name = "create_users_table"
162+
163+ [[actions]]
164+ type = "create_table"
165+ name = "users"
166+ primary_key = ["id"]
167+
168+ [[actions.columns]]
169+ name = "id"
170+ type = "INTEGER"
171+
172+ [[actions.columns]]
173+ name = "data"
174+ type = "JSONB"
175+ "#,
176176 );
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− });
186177
187− let first_migrations = vec![create_table_migration.clone()];
188− let second_migrations = vec![create_table_migration.clone(), add_index_migration.clone()];
189−
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();
178+ test.second_migration(
179+ r#"
180+ name = "add_data_index"
224181
225− assert!(is_ready, "expected index to be ready");
226− assert!(is_valid, "expected index to be valid");
227− assert_eq!("gin", index_type, "expected index type to be GIN");
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+
217+ assert!(is_ready, "expected index to be ready");
218+ assert!(is_valid, "expected index to be valid");
219+ assert_eq!("gin", index_type, "expected index type to be GIN");
220+ });
228221
229− reshape.complete().unwrap();
230− common::assert_cleaned_up(&mut new_db);
222+ test.run();
231223}
tests/alter_column.rs+382−501
@@ -1,61 +1,45 @@
1−use reshape::migrations::{
2− AddIndex, AlterColumn, ColumnBuilder, ColumnChanges, CreateTableBuilder, Index, Migration,
3−};
4−
51mod common;
2+use common::Test;
63
74#[test]
85fn alter_column_data() {
9− let (mut reshape, mut old_db, mut new_db) = common::setup();
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− });
6+ let mut test = Test::new("Alter column");
427
43− let first_migrations = vec![create_users_table.clone()];
44− let second_migrations = vec![create_users_table.clone(), uppercase_name.clone()];
8+ test.first_migration(
9+ r#"
10+ name = "create_user_table"
4511
46− // Run first migration, should automatically finish
47− reshape.migrate(first_migrations.clone()).unwrap();
12+ [[actions]]
13+ type = "create_table"
14+ name = "users"
15+ primary_key = ["id"]
4816
49− // Update search paths
50− old_db
51− .simple_query(&reshape::schema_query_for_migration(
52− &first_migrations.last().unwrap().name,
53− ))
54− .unwrap();
17+ [[actions.columns]]
18+ name = "id"
19+ type = "INTEGER"
20+
21+ [[actions.columns]]
22+ name = "name"
23+ type = "TEXT"
24+ "#,
25+ );
5526
56− // Insert some test users
57− old_db
58− .simple_query(
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| {
41+ // Insert some test users
42+ db.simple_query(
5943 "
6044 INSERT INTO users (id, name) VALUES
6145 (1, 'john Doe'),
@@ -63,98 +47,82 @@ fn alter_column_data() {
6347 ",
6448 )
6549 .unwrap();
50+ });
6651
67− // Run second migration
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
76− let expected = vec!["JOHN DOE", "JANE DOE"];
77− assert!(new_db
78− .query("SELECT name FROM users ORDER BY id", &[],)
79− .unwrap()
80− .iter()
81− .map(|row| row.get::<_, String>("name"))
82− .eq(expected));
83−
84− // Insert data using old schema and make sure the new schema gets correct values
85− old_db
86− .simple_query("INSERT INTO users (id, name) VALUES (3, 'test testsson')")
87− .unwrap();
88− let result = new_db
89− .query_one("SELECT name from users WHERE id = 3", &[])
90− .unwrap();
91− assert_eq!("TEST TESTSSON", result.get::<_, &str>("name"));
92−
93− // Insert data using new schema and make sure the old schema gets correct values
94− new_db
95− .simple_query("INSERT INTO users (id, name) VALUES (4, 'TEST TESTSSON')")
96− .unwrap();
97− let result = old_db
98− .query_one("SELECT name from users WHERE id = 4", &[])
99− .unwrap();
100− assert_eq!("test testsson", result.get::<_, &str>("name"));
52+ test.intermediate(|old_db, new_db| {
53+ // Check that the existing users has the altered data
54+ let expected = vec!["JOHN DOE", "JANE DOE"];
55+ assert!(new_db
56+ .query("SELECT name FROM users ORDER BY id", &[],)
57+ .unwrap()
58+ .iter()
59+ .map(|row| row.get::<_, String>("name"))
60+ .eq(expected));
61+
62+ // Insert data using old schema and make sure the new schema gets correct values
63+ old_db
64+ .simple_query("INSERT INTO users (id, name) VALUES (3, 'test testsson')")
65+ .unwrap();
66+ let result = new_db
67+ .query_one("SELECT name from users WHERE id = 3", &[])
68+ .unwrap();
69+ assert_eq!("TEST TESTSSON", result.get::<_, &str>("name"));
70+
71+ // Insert data using new schema and make sure the old schema gets correct values
72+ new_db
73+ .simple_query("INSERT INTO users (id, name) VALUES (4, 'TEST TESTSSON')")
74+ .unwrap();
75+ let result = old_db
76+ .query_one("SELECT name from users WHERE id = 4", &[])
77+ .unwrap();
78+ assert_eq!("test testsson", result.get::<_, &str>("name"));
79+ });
10180
102− reshape.complete().unwrap();
103− common::assert_cleaned_up(&mut new_db);
81+ test.run();
10482}
10583
10684#[test]
10785fn alter_column_set_not_null() {
108− let (mut reshape, mut old_db, mut new_db) = common::setup();
109−
110− let create_users_table = Migration::new("create_user_table", None).with_action(
111− CreateTableBuilder::default()
112− .name("users")
113− .primary_key(vec!["id".to_string()])
114− .columns(vec![
115− ColumnBuilder::default()
116− .name("id")
117− .data_type("INTEGER")
118− .build()
119− .unwrap(),
120− ColumnBuilder::default()
121− .name("name")
122− .data_type("TEXT")
123− .build()
124− .unwrap(),
125− ])
126− .build()
127− .unwrap(),
86+ let mut test = Test::new("Set column not null");
87+
88+ test.first_migration(
89+ r#"
90+ name = "create_user_table"
91+
92+ [[actions]]
93+ type = "create_table"
94+ name = "users"
95+ primary_key = ["id"]
96+
97+ [[actions.columns]]
98+ name = "id"
99+ type = "INTEGER"
100+
101+ [[actions.columns]]
102+ name = "name"
103+ type = "TEXT"
104+ "#,
128105 );
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− });
141106
142− let first_migrations = vec![create_users_table.clone()];
143− let second_migrations = vec![create_users_table.clone(), set_name_not_null.clone()];
107+ test.second_migration(
108+ r#"
109+ name = "set_name_not_null"
144110
145− // Run first migration, should automatically finish
146− reshape.migrate(first_migrations.clone()).unwrap();
111+ [[actions]]
112+ type = "alter_column"
113+ table = "users"
114+ column = "name"
115+ up = "COALESCE(name, 'TEST_DEFAULT_VALUE')"
116+ down = "name"
147117
148− // Update search paths
149− old_db
150− .simple_query(&reshape::schema_query_for_migration(
151− &first_migrations.last().unwrap().name,
152− ))
153− .unwrap();
118+ [actions.changes]
119+ nullable = false
120+ "#,
121+ );
154122
155− // Insert some test users
156− old_db
157− .simple_query(
123+ test.after_first(|db| {
124+ // Insert some test users
125+ db.simple_query(
158126 "
159127 INSERT INTO users (id, name) VALUES
160128 (1, 'John Doe'),
@@ -162,99 +130,80 @@ fn alter_column_set_not_null() {
162130 ",
163131 )
164132 .unwrap();
133+ });
165134
166− // Run second migration
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
175− let expected = vec!["John Doe", "TEST_DEFAULT_VALUE"];
176− assert!(new_db
177− .query("SELECT name FROM users ORDER BY id", &[],)
178− .unwrap()
179− .iter()
180− .map(|row| row.get::<_, String>("name"))
181− .eq(expected));
182−
183− // Insert data using old schema and make sure the new schema gets correct values
184− old_db
185− .simple_query("INSERT INTO users (id, name) VALUES (3, NULL)")
186− .unwrap();
187− let result = new_db
188− .query_one("SELECT name from users WHERE id = 3", &[])
189− .unwrap();
190− assert_eq!("TEST_DEFAULT_VALUE", result.get::<_, &str>("name"));
191−
192− // Insert data using new schema and make sure the old schema gets correct values
193− new_db
194− .simple_query("INSERT INTO users (id, name) VALUES (4, 'Jane Doe')")
195− .unwrap();
196− let result = old_db
197− .query_one("SELECT name from users WHERE id = 4", &[])
198− .unwrap();
199− assert_eq!("Jane Doe", result.get::<_, &str>("name"));
135+ test.intermediate(|old_db, new_db| {
136+ // Check that existing users got the correct values
137+ let expected = vec!["John Doe", "TEST_DEFAULT_VALUE"];
138+ assert!(new_db
139+ .query("SELECT name FROM users ORDER BY id", &[],)
140+ .unwrap()
141+ .iter()
142+ .map(|row| row.get::<_, String>("name"))
143+ .eq(expected));
144+
145+ // Insert data using old schema and make sure the new schema gets correct values
146+ old_db
147+ .simple_query("INSERT INTO users (id, name) VALUES (3, NULL)")
148+ .unwrap();
149+ let result = new_db
150+ .query_one("SELECT name from users WHERE id = 3", &[])
151+ .unwrap();
152+ assert_eq!("TEST_DEFAULT_VALUE", result.get::<_, &str>("name"));
153+
154+ // Insert data using new schema and make sure the old schema gets correct values
155+ new_db
156+ .simple_query("INSERT INTO users (id, name) VALUES (4, 'Jane Doe')")
157+ .unwrap();
158+ let result = old_db
159+ .query_one("SELECT name from users WHERE id = 4", &[])
160+ .unwrap();
161+ assert_eq!("Jane Doe", result.get::<_, &str>("name"));
162+ });
200163
201− reshape.complete().unwrap();
202− common::assert_cleaned_up(&mut new_db);
164+ test.run();
203165}
204166
205167#[test]
206168fn alter_column_rename() {
207− let (mut reshape, mut old_db, mut new_db) = common::setup();
208−
209− let create_users_table = Migration::new("create_user_table", None).with_action(
210− CreateTableBuilder::default()
211− .name("users")
212− .primary_key(vec!["id".to_string()])
213− .columns(vec![
214− ColumnBuilder::default()
215− .name("id")
216− .data_type("INTEGER")
217− .build()
218− .unwrap(),
219− ColumnBuilder::default()
220− .name("name")
221− .data_type("TEXT")
222− .build()
223− .unwrap(),
224− ])
225− .build()
226− .unwrap(),
169+ let mut test = Test::new("Rename column");
170+
171+ test.first_migration(
172+ r#"
173+ name = "create_user_table"
174+
175+ [[actions]]
176+ type = "create_table"
177+ name = "users"
178+ primary_key = ["id"]
179+
180+ [[actions.columns]]
181+ name = "id"
182+ type = "INTEGER"
183+
184+ [[actions.columns]]
185+ name = "name"
186+ type = "TEXT"
187+ "#,
188+ );
189+
190+ test.second_migration(
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+ "#,
227202 );
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();
254203
255− // Insert some test data
256− old_db
257− .simple_query(
204+ test.after_first(|db| {
205+ // Insert some test data
206+ db.simple_query(
258207 "
259208 INSERT INTO users (id, name) VALUES
260209 (1, 'John Doe'),
@@ -262,94 +211,69 @@ fn alter_column_rename() {
262211 ",
263212 )
264213 .unwrap();
214+ });
265215
266− // Run second migration
267− reshape.migrate(second_migrations.clone()).unwrap();
268− new_db
269− .simple_query(&reshape::schema_query_for_migration(
270− &second_migrations.last().unwrap().name,
271− ))
272− .unwrap();
216+ test.intermediate(|_, new_db| {
217+ // Check that existing values can be queried using new column name
218+ let expected = vec!["John Doe", "Jane Doe"];
219+ assert!(new_db
220+ .query("SELECT full_name FROM users ORDER BY id", &[],)
221+ .unwrap()
222+ .iter()
223+ .map(|row| row.get::<_, String>("full_name"))
224+ .eq(expected));
225+ });
273226
274− // Check that existing values can be queried using new column name
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);
227+ test.run();
285228}
286229
287230#[test]
288231fn alter_column_multiple() {
289− let (mut reshape, mut old_db, mut new_db) = common::setup();
290−
291− let create_users_table = Migration::new("create_user_table", None).with_action(
292− CreateTableBuilder::default()
293− .name("users")
294− .primary_key(vec!["id".to_string()])
295− .columns(vec![
296− ColumnBuilder::default()
297− .name("id")
298− .data_type("INTEGER")
299− .build()
300− .unwrap(),
301− ColumnBuilder::default()
302− .name("counter")
303− .data_type("INTEGER")
304− .nullable(false)
305− .build()
306− .unwrap(),
307− ])
308− .build()
309− .unwrap(),
232+ let mut test = Test::new("Alter column value multiple times");
233+
234+ test.first_migration(
235+ r#"
236+ name = "create_user_table"
237+
238+ [[actions]]
239+ type = "create_table"
240+ name = "users"
241+ primary_key = ["id"]
242+
243+ [[actions.columns]]
244+ name = "id"
245+ type = "INTEGER"
246+
247+ [[actions.columns]]
248+ name = "counter"
249+ type = "INTEGER"
250+ nullable = false
251+ "#,
252+ );
253+
254+ test.second_migration(
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+ "#,
310272 );
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();
349273
350− // Insert some test data
351− old_db
352− .simple_query(
274+ test.after_first(|db| {
275+ // Insert some test data
276+ db.simple_query(
353277 "
354278 INSERT INTO users (id, counter) VALUES
355279 (1, 0),
@@ -357,239 +281,196 @@ fn alter_column_multiple() {
357281 ",
358282 )
359283 .unwrap();
284+ });
360285
361− // Run second migration
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
370− let expected = vec![2, 102];
371− let results: Vec<i32> = new_db
372− .query("SELECT counter FROM users ORDER BY id", &[])
373− .unwrap()
374− .iter()
375− .map(|row| row.get::<_, i32>("counter"))
376− .collect();
377− assert_eq!(expected, results);
378−
379− // Update data using old schema and make sure it was updated for the new schema
380− old_db
381− .query("UPDATE users SET counter = 50 WHERE id = 1", &[])
382− .unwrap();
383− let result: i32 = new_db
384− .query("SELECT counter FROM users WHERE id = 1", &[])
385− .unwrap()
386− .iter()
387− .map(|row| row.get("counter"))
388− .nth(0)
389− .unwrap();
390− assert_eq!(52, result);
391−
392− // Update data using new schema and make sure it was updated for the old schema
393− new_db
394− .query("UPDATE users SET counter = 50 WHERE id = 1", &[])
395− .unwrap();
396− let result: i32 = old_db
397− .query("SELECT counter FROM users WHERE id = 1", &[])
398− .unwrap()
399− .iter()
400− .map(|row| row.get("counter"))
401− .nth(0)
402− .unwrap();
403− assert_eq!(48, result);
286+ test.intermediate(|old_db, new_db| {
287+ // Check that the existing data has been updated
288+ let expected = vec![2, 102];
289+ let results: Vec<i32> = new_db
290+ .query("SELECT counter FROM users ORDER BY id", &[])
291+ .unwrap()
292+ .iter()
293+ .map(|row| row.get::<_, i32>("counter"))
294+ .collect();
295+ assert_eq!(expected, results);
296+
297+ // Update data using old schema and make sure it was updated for the new schema
298+ old_db
299+ .query("UPDATE users SET counter = 50 WHERE id = 1", &[])
300+ .unwrap();
301+ let result: i32 = new_db
302+ .query("SELECT counter FROM users WHERE id = 1", &[])
303+ .unwrap()
304+ .iter()
305+ .map(|row| row.get("counter"))
306+ .nth(0)
307+ .unwrap();
308+ assert_eq!(52, result);
309+
310+ // Update data using new schema and make sure it was updated for the old schema
311+ new_db
312+ .query("UPDATE users SET counter = 50 WHERE id = 1", &[])
313+ .unwrap();
314+ let result: i32 = old_db
315+ .query("SELECT counter FROM users WHERE id = 1", &[])
316+ .unwrap()
317+ .iter()
318+ .map(|row| row.get("counter"))
319+ .nth(0)
320+ .unwrap();
321+ assert_eq!(48, result);
322+ });
404323
405− reshape.complete().unwrap();
406− common::assert_cleaned_up(&mut new_db);
324+ test.run();
407325}
408326
409327#[test]
410328fn alter_column_default() {
411− let (mut reshape, mut old_db, mut new_db) = common::setup();
412−
413− let create_users_table = Migration::new("create_user_table", None).with_action(
414− CreateTableBuilder::default()
415− .name("users")
416− .primary_key(vec!["id".to_string()])
417− .columns(vec![
418− ColumnBuilder::default()
419− .name("id")
420− .data_type("INTEGER")
421− .build()
422− .unwrap(),
423− ColumnBuilder::default()
424− .name("name")
425− .data_type("TEXT")
426− .nullable(false)
427− .default_value("'DEFAULT'")
428− .build()
429− .unwrap(),
430− ])
431− .build()
432− .unwrap(),
329+ let mut test = Test::new("Change default value for column");
330+
331+ test.first_migration(
332+ r#"
333+ name = "create_user_table"
334+
335+ [[actions]]
336+ type = "create_table"
337+ name = "users"
338+ primary_key = ["id"]
339+
340+ [[actions.columns]]
341+ name = "id"
342+ type = "INTEGER"
343+
344+ [[actions.columns]]
345+ name = "name"
346+ type = "TEXT"
347+ nullable = false
348+ default = "'DEFAULT'"
349+ "#,
433350 );
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();
460351
461− // Insert a test user
462− old_db
463− .simple_query(
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| {
367+ // Insert a test user
368+ db.simple_query(
464369 "
465370 INSERT INTO users (id) VALUES (1)
466371 ",
467372 )
468373 .unwrap();
374+ });
469375
470− // Run second migration
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
479− let expected = vec!["DEFAULT"];
480− assert!(new_db
481− .query("SELECT name FROM users", &[],)
482− .unwrap()
483− .iter()
484− .map(|row| row.get::<_, String>("name"))
485− .eq(expected));
486−
487− // Insert data using old schema and make those get the old default value
488− old_db
489− .simple_query("INSERT INTO users (id) VALUES (2)")
490− .unwrap();
491− let result = new_db
492− .query_one("SELECT name from users WHERE id = 2", &[])
493− .unwrap();
494− assert_eq!("DEFAULT", result.get::<_, &str>("name"));
495−
496− // Insert data using new schema and make sure it gets the new default value
497− new_db
498− .simple_query("INSERT INTO users (id) VALUES (3)")
499− .unwrap();
500− let result = old_db
501− .query_one("SELECT name from users WHERE id = 3", &[])
502− .unwrap();
503− assert_eq!("NEW DEFAULT", result.get::<_, &str>("name"));
376+ test.intermediate(|old_db, new_db| {
377+ // Check that the existing users has the old default value
378+ let expected = vec!["DEFAULT"];
379+ assert!(new_db
380+ .query("SELECT name FROM users", &[],)
381+ .unwrap()
382+ .iter()
383+ .map(|row| row.get::<_, String>("name"))
384+ .eq(expected));
385+
386+ // Insert data using old schema and make those get the old default value
387+ old_db
388+ .simple_query("INSERT INTO users (id) VALUES (2)")
389+ .unwrap();
390+ let result = new_db
391+ .query_one("SELECT name from users WHERE id = 2", &[])
392+ .unwrap();
393+ assert_eq!("DEFAULT", result.get::<_, &str>("name"));
394+
395+ // Insert data using new schema and make sure it gets the new default value
396+ new_db
397+ .simple_query("INSERT INTO users (id) VALUES (3)")
398+ .unwrap();
399+ let result = old_db
400+ .query_one("SELECT name from users WHERE id = 3", &[])
401+ .unwrap();
402+ assert_eq!("NEW DEFAULT", result.get::<_, &str>("name"));
403+ });
504404
505− reshape.complete().unwrap();
506− common::assert_cleaned_up(&mut new_db);
405+ test.run();
507406}
508407
509408#[test]
510409fn alter_column_with_index() {
511− let (mut reshape, mut db, _) = common::setup();
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− });
410+ let mut test = Test::new("Alter column with index");
559411
560− let first_migrations = vec![create_users_table.clone()];
561− let second_migrations = vec![create_users_table.clone(), uppercase_name.clone()];
412+ test.first_migration(
413+ r#"
414+ name = "create_user_table"
562415
563− // Run first migration, should automatically finish
564− reshape.migrate(first_migrations.clone()).unwrap();
416+ [[actions]]
417+ type = "create_table"
418+ name = "users"
419+ primary_key = ["id"]
565420
566− // Run second migration
567− reshape.migrate(second_migrations.clone()).unwrap();
568− db.simple_query(&reshape::schema_query_for_migration(
569− &second_migrations.last().unwrap().name,
570− ))
571− .unwrap();
421+ [[actions.columns]]
422+ name = "id"
423+ type = "INTEGER"
572424
573− // Complete the second migration which should replace the existing column
574− // with the temporary one
575− reshape.complete().unwrap();
425+ [[actions.columns]]
426+ name = "first_name"
427+ type = "TEXT"
576428
577− // Make sure index still exists
578− let result: i64 = db
579− .query(
580− "
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| {
457+ // Make sure index still exists
458+ let result: i64 = db
459+ .query(
460+ "
581461 SELECT COUNT(*)
582462 FROM pg_catalog.pg_index
583463 JOIN pg_catalog.pg_class ON pg_index.indexrelid = pg_class.oid
584464 WHERE pg_class.relname = 'users_name_idx'
585465 ",
586− &[],
587− )
588− .unwrap()
589− .first()
590− .map(|row| row.get(0))
591− .unwrap();
592− assert_eq!(1, result, "expected index to still exist");
466+ &[],
467+ )
468+ .unwrap()
469+ .first()
470+ .map(|row| row.get(0))
471+ .unwrap();
472+ assert_eq!(1, result, "expected index to still exist");
473+ });
593474
594− common::assert_cleaned_up(&mut db);
475+ test.run();
595476}
tests/common.rs+29−18
@@ -10,6 +10,7 @@ pub struct Test<'a> {
1010
1111 first_migration: Option<Migration>,
1212 second_migration: Option<Migration>,
13+ expect_failure: bool,
1314
1415 after_first_fn: Option<fn(&mut Client) -> ()>,
1516 intermediate_fn: Option<fn(&mut Client, &mut Client) -> ()>,
@@ -34,6 +35,7 @@ impl Test<'_> {
3435 new_db,
3536 first_migration: None,
3637 second_migration: None,
38+ expect_failure: false,
3739 after_first_fn: None,
3840 intermediate_fn: None,
3941 after_completion_fn: None,
@@ -46,31 +48,41 @@ impl Test<'_> {
4648 self
4749 }
4850
51+ #[allow(dead_code)]
4952 pub fn second_migration(&mut self, migration: &str) -> &mut Self {
5053 self.second_migration = Some(Self::parse_migration(migration));
5154 self
5255 }
5356
57+ #[allow(dead_code)]
5458 pub fn after_first(&mut self, f: fn(&mut Client) -> ()) -> &mut Self {
5559 self.after_first_fn = Some(f);
5660 self
5761 }
5862
63+ #[allow(dead_code)]
5964 pub fn intermediate(&mut self, f: fn(&mut Client, &mut Client) -> ()) -> &mut Self {
6065 self.intermediate_fn = Some(f);
6166 self
6267 }
6368
69+ #[allow(dead_code)]
6470 pub fn after_completion(&mut self, f: fn(&mut Client) -> ()) -> &mut Self {
6571 self.after_completion_fn = Some(f);
6672 self
6773 }
6874
75+ #[allow(dead_code)]
6976 pub fn after_abort(&mut self, f: fn(&mut Client) -> ()) -> &mut Self {
7077 self.after_abort_fn = Some(f);
7178 self
7279 }
7380
81+ #[allow(dead_code)]
82+ pub fn expect_failure(&mut self) {
83+ self.expect_failure = true;
84+ }
85+
7486 fn parse_migration(encoded: &str) -> Migration {
7587 toml::from_str(encoded).unwrap()
7688 }
@@ -83,6 +95,7 @@ enum RunType {
8395}
8496
8597impl Test<'_> {
98+ #[allow(dead_code)]
8699 pub fn run(&mut self) {
87100 if self.second_migration.is_some() {
88101 // Run to completion
@@ -124,10 +137,21 @@ impl Test<'_> {
124137
125138 // Apply second migration
126139 if let Some(second_migration) = &self.second_migration {
127− print_subheading("Applying second migration");
128− self.reshape
129− .migrate(vec![first_migration.clone(), second_migration.clone()])
130− .unwrap();
140+ if self.expect_failure {
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");
151+ self.reshape
152+ .migrate(vec![first_migration.clone(), second_migration.clone()])
153+ .unwrap();
154+ }
131155
132156 // Update search path
133157 self.new_db
@@ -191,7 +215,7 @@ fn print_success() {
191215}
192216
193217fn add_spacer(text: &str, char: &str) -> String {
194− const TARGET_WIDTH: usize = 50;
218+ const TARGET_WIDTH: usize = 80;
195219 let num_of_chars = (TARGET_WIDTH - text.len() - 2) / 2;
196220 let spacer = std::iter::repeat(char)
197221 .take(num_of_chars)
@@ -202,19 +226,6 @@ fn add_spacer(text: &str, char: &str) -> String {
202226 format!("{spacer} {text} {spacer}{extra}", spacer = spacer)
203227}
204228
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−
218229pub fn assert_cleaned_up(db: &mut Client) {
219230 // Make sure no temporary columns remain
220231 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−
31mod common;
2+use common::Test;
43
54#[test]
65fn create_table() {
7− let mut test = common::Test::new("Create table");
6+ let mut test = Test::new("Create table");
87
98 test.first_migration(
109 r#"
@@ -113,59 +112,57 @@ fn create_table() {
113112
114113#[test]
115114fn create_table_with_foreign_keys() {
116− let (mut reshape, mut db, _) = common::setup();
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− );
115+ let mut test = Test::new("Create table");
131116
132− let create_second_table_migration = Migration::new("create_items_table", None).with_action(
133− CreateTableBuilder::default()
134− .name("items")
135− .primary_key(vec!["id".to_string()])
136− .foreign_keys(vec![ForeignKey {
137− columns: vec!["user_id".to_string()],
138− referenced_table: "users".to_string(),
139− referenced_columns: vec!["id".to_string()],
140− }])
141− .columns(vec![
142− ColumnBuilder::default()
143− .name("id")
144− .data_type("INTEGER")
145− .generated("ALWAYS AS IDENTITY")
146− .build()
147− .unwrap(),
148− ColumnBuilder::default()
149− .name("user_id")
150− .data_type("INTEGER")
151− .nullable(false)
152− .build()
153− .unwrap(),
154− ])
155− .build()
156− .unwrap(),
157− );
117+ test.first_migration(
118+ r#"
119+ name = "create_users_table"
120+
121+ [[actions]]
122+ type = "create_table"
123+ name = "users"
124+ primary_key = ["id"]
125+
126+ [[actions.columns]]
127+ name = "id"
128+ type = "INTEGER"
129+ generated = "ALWAYS AS IDENTITY"
130+
131+ [[actions.columns]]
132+ name = "name"
133+ type = "TEXT"
134+
135+ [[actions.columns]]
136+ name = "created_at"
137+ type = "TIMESTAMP"
138+ nullable = false
139+ default = "NOW()"
158140
159− reshape
160− .migrate(vec![
161− create_table_migration.clone(),
162− create_second_table_migration.clone(),
163− ])
164− .unwrap();
141+ [[actions]]
142+ type = "create_table"
143+ name = "items"
144+ primary_key = ["id"]
145+
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+ );
165161
166− let foreign_key_columns: Vec<(String, String, String)> = db
167− .query(
168− "
162+ test.after_first(|db| {
163+ let foreign_key_columns: Vec<(String, String, String)> = db
164+ .query(
165+ "
169166 SELECT
170167 kcu.column_name,
171168 ccu.table_name AS foreign_table_name,
@@ -180,23 +177,24 @@ fn create_table_with_foreign_keys() {
180177 AND ccu.table_schema = tc.table_schema
181178 WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_name='items';
182179 ",
183− &[],
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"),
180+ &[],
192181 )
193− })
194− .collect();
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+ )
190+ })
191+ .collect();
195192
196− assert_eq!(
197− vec![("user_id".to_string(), "users".to_string(), "id".to_string())],
198− foreign_key_columns
199− );
193+ assert_eq!(
194+ vec![("user_id".to_string(), "users".to_string(), "id".to_string())],
195+ foreign_key_columns
196+ );
197+ });
200198
201− common::assert_cleaned_up(&mut db);
199+ test.run();
202200}
tests/failure.rs+34−58
@@ -1,75 +1,51 @@
1−use reshape::migrations::{AddColumn, Column, ColumnBuilder, CreateTableBuilder, Migration};
2−
31mod common;
2+use common::Test;
43
54#[test]
65fn 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"]
816
9− let create_users_table = Migration::new("create_users_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− .build()
23− .unwrap(),
24− ])
25− .build()
26− .unwrap(),
17+ [[actions.columns]]
18+ name = "id"
19+ type = "INTEGER"
20+ "#,
2721 );
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− });
3922
40− let first_migrations = vec![create_users_table.clone()];
41− let second_migrations = vec![create_users_table.clone(), add_first_column.clone()];
23+ test.second_migration(
24+ r#"
25+ name = "add_invalid_column"
4226
43− // Run first migration, should automatically finish
44− reshape.migrate(first_migrations.clone()).unwrap();
27+ [[actions]]
28+ type = "add_column"
29+ table = "users"
4530
46− // Update search paths
47− old_db
48− .simple_query(&reshape::schema_query_for_migration(
49− &first_migrations.last().unwrap().name,
50− ))
51− .unwrap();
52− new_db
53− .simple_query(&reshape::schema_query_for_migration(
54− &first_migrations.last().unwrap().name,
55− ))
56− .unwrap();
31+ up = "INVALID SQL"
32+
33+ [actions.column]
34+ name = "first"
35+ type = "TEXT"
36+ "#,
37+ );
5738
5839 // Insert a test user
59− new_db
60− .simple_query(
40+ test.after_first(|db| {
41+ db.simple_query(
6142 "
62− INSERT INTO users (id, name) VALUES
63− (1, 'John Doe')
43+ INSERT INTO users (id) VALUES (1)
6444 ",
6545 )
6646 .unwrap();
47+ });
6748
68− // Run second migration and ensure it fails
69− assert!(
70− reshape.migrate(second_migrations.clone()).is_err(),
71− "invalid migration succeeded"
72− );
73−
74− common::assert_cleaned_up(&mut new_db);
49+ test.expect_failure();
50+ test.run();
7551}
tests/remove_column.rs+54−68
@@ -1,81 +1,67 @@
1−use reshape::migrations::{ColumnBuilder, CreateTableBuilder, Migration, RemoveColumn};
2−
31mod common;
2+use common::Test;
43
54#[test]
65fn remove_column() {
7− let (mut reshape, mut old_db, mut new_db) = common::setup();
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− });
6+ let mut test = Test::new("Remove column");
357
36− let first_migrations = vec![create_table_migration.clone()];
37− let second_migrations = vec![
38− create_table_migration.clone(),
39− remove_column_migration.clone(),
40− ];
8+ test.first_migration(
9+ r#"
10+ name = "create_user_table"
4111
42− // Run migrations
43− reshape.migrate(first_migrations.clone()).unwrap();
44− reshape.migrate(second_migrations.clone()).unwrap();
12+ [[actions]]
13+ type = "create_table"
14+ name = "users"
15+ primary_key = ["id"]
4516
46− // Update schemas of Postgres connections
47− let old_schema_query =
48− reshape::schema_query_for_migration(&first_migrations.last().unwrap().name);
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();
17+ [[actions.columns]]
18+ name = "id"
19+ type = "INTEGER"
5320
54− // Insert using old schema and ensure it can be retrieved through new schema
55− old_db
56− .simple_query("INSERT INTO users(id, name) VALUES (1, 'John Doe')")
57− .unwrap();
58− let results = new_db
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"));
21+ [[actions.columns]]
22+ name = "name"
23+ type = "TEXT"
24+ "#,
25+ );
6326
64− // Ensure the name column is not accesible through the new schema
65− assert!(new_db.query("SELECT id, name FROM users", &[]).is_err());
27+ test.second_migration(
28+ r#"
29+ name = "remove_name_column"
6630
67− // Insert using new schema and ensure the down function is correctly applied
68− new_db
69− .simple_query("INSERT INTO users(id) VALUES (2)")
70− .unwrap();
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"),
76− result.as_ref().map(|row| row.get("name"))
31+ [[actions]]
32+ type = "remove_column"
33+ table = "users"
34+ column = "name"
35+ down = "'TEST_DOWN_VALUE'"
36+ "#,
7737 );
7838
79− reshape.complete().unwrap();
80− common::assert_cleaned_up(&mut new_db);
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')")
43+ .unwrap();
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();
8167}
tests/remove_index.rs+68−97
@@ -1,115 +1,86 @@
1−use reshape::migrations::{
2− AddIndex, ColumnBuilder, CreateTableBuilder, Index, Migration, RemoveIndex,
3−};
4−
51mod common;
2+use common::Test;
63
74#[test]
85fn remove_index() {
9− let (mut reshape, _, mut db) = common::setup();
6+ let mut test = Test::new("Remove index");
107
11− let create_table_migration = Migration::new("create_user_table", None)
12− .with_action(
13− CreateTableBuilder::default()
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− });
8+ test.first_migration(
9+ r#"
10+ name = "create_users_table"
4011
41− let remove_index_migration =
42− Migration::new("remove_name_index", None).with_action(RemoveIndex {
43− index: "name_idx".to_string(),
44− });
12+ [[actions]]
13+ type = "create_table"
14+ name = "users"
15+ primary_key = ["id"]
4516
46− let first_migrations = vec![create_table_migration.clone()];
47− let second_migrations = vec![
48− create_table_migration.clone(),
49− remove_index_migration.clone(),
50− ];
17+ [[actions.columns]]
18+ name = "id"
19+ type = "INTEGER"
5120
52− // Run migrations
53− reshape.migrate(first_migrations.clone()).unwrap();
21+ [[actions.columns]]
22+ name = "name"
23+ type = "TEXT"
5424
55− // Ensure index is valid and ready
56− let result: Vec<(bool, bool)> = db
57− .query(
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();
25+ [[actions]]
26+ type = "add_index"
27+ table = "users"
7028
71− assert_eq!(vec![(true, true)], result);
29+ [actions.index]
30+ name = "name_idx"
31+ columns = ["name"]
32+ "#,
33+ );
7234
73− // Run migration to remove index
74− reshape.migrate(second_migrations.clone()).unwrap();
35+ test.second_migration(
36+ r#"
37+ name = "remove_name_index"
7538
76− // Ensure index is still valid and ready during the migration
77− let result: Vec<(bool, bool)> = db
78− .query(
79− "
80− SELECT pg_index.indisready, pg_index.indisvalid
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();
39+ [[actions]]
40+ type = "remove_index"
41+ index = "name_idx"
42+ "#,
43+ );
9144
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();
9361
94− reshape.complete().unwrap();
62+ assert_eq!(vec![(true, true)], result);
63+ });
9564
96− // Ensure index has been removed after the migration is complete
97− let count: i64 = db
98− .query(
99− "
100− SELECT COUNT(*)
101− FROM pg_catalog.pg_index
102− JOIN pg_catalog.pg_class ON pg_index.indexrelid = pg_class.oid
103− WHERE pg_class.relname = 'name_idx'
104− ",
105− &[],
106− )
107− .unwrap()
108− .first()
109− .map(|row| row.get(0))
110− .unwrap();
65+ test.after_completion(|db| {
66+ // Ensure index has been removed after the migration is complete
67+ let count: i64 = db
68+ .query(
69+ "
70+ SELECT COUNT(*)
71+ FROM pg_catalog.pg_index
72+ JOIN pg_catalog.pg_class ON pg_index.indexrelid = pg_class.oid
73+ WHERE pg_class.relname = 'name_idx'
74+ ",
75+ &[],
76+ )
77+ .unwrap()
78+ .first()
79+ .map(|row| row.get(0))
80+ .unwrap();
11181
112− assert_eq!(0, count, "expected index to not exist");
82+ assert_eq!(0, count, "expected index to not exist");
83+ });
11384
114− common::assert_cleaned_up(&mut db);
85+ test.run();
11586}
tests/remove_table.rs+32−44
@@ -1,54 +1,42 @@
1−use reshape::migrations::{ColumnBuilder, CreateTableBuilder, Migration, RemoveTable};
2−
31mod common;
2+use common::Test;
43
54#[test]
65fn remove_table() {
7− let (mut reshape, mut old_db, mut new_db) = common::setup();
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(),
6+ let mut test = Test::new("Remove table");
7+
8+ test.first_migration(
9+ r#"
10+ name = "create_users_table"
11+
12+ [[actions]]
13+ type = "create_table"
14+ name = "users"
15+ primary_key = ["id"]
16+
17+ [[actions.columns]]
18+ name = "id"
19+ type = "INTEGER"
20+ "#,
2021 );
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− ];
3122
32− // Run migrations
33− reshape.migrate(first_migrations.clone()).unwrap();
34− reshape.migrate(second_migrations.clone()).unwrap();
23+ test.second_migration(
24+ r#"
25+ name = "remove_users_table"
3526
36− // Update schemas of Postgres connections
37− let old_schema_query =
38− reshape::schema_query_for_migration(&first_migrations.last().unwrap().name);
39− let new_schema_query =
40− reshape::schema_query_for_migration(&second_migrations.last().unwrap().name);
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();
27+ [[actions]]
28+ type = "remove_table"
29+ table = "users"
30+ "#,
31+ );
4832
49− // Ensure the table is not accessible through the new schema
50− assert!(new_db.query("SELECT id FROM users", &[]).is_err());
33+ test.intermediate(|old_db, new_db| {
34+ // Make sure inserts work against the old schema
35+ old_db
36+ .simple_query("INSERT INTO users(id) VALUES (1)")
37+ .unwrap();
5138
52− reshape.complete().unwrap();
53− common::assert_cleaned_up(&mut new_db);
39+ // Ensure the table is not accessible through the new schema
40+ assert!(new_db.query("SELECT id FROM users", &[]).is_err());
41+ });
5442}
tests/rename_table.rs+56−66
@@ -1,80 +1,70 @@
1−use reshape::migrations::{ColumnBuilder, CreateTableBuilder, Migration, RenameTable};
2−
31mod common;
2+use common::Test;
43
54#[test]
65fn rename_table() {
7− let (mut reshape, mut old_db, mut new_db) = common::setup();
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− });
6+ let mut test = Test::new("Rename table");
267
27− let first_migrations = vec![create_table_migration.clone()];
28− let second_migrations = vec![
29− create_table_migration.clone(),
30− rename_table_migration.clone(),
31− ];
8+ test.first_migration(
9+ r#"
10+ name = "create_users_table"
11+
12+ [[actions]]
13+ type = "create_table"
14+ name = "users"
15+ primary_key = ["id"]
3216
33− // Run migrations
34− reshape.migrate(first_migrations.clone()).unwrap();
35− reshape.migrate(second_migrations.clone()).unwrap();
17+ [[actions.columns]]
18+ name = "id"
19+ type = "INTEGER"
20+ "#,
21+ );
3622
37− // Update schemas of Postgres connections
38− let old_schema_query =
39− reshape::schema_query_for_migration(&first_migrations.last().unwrap().name);
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();
23+ test.second_migration(
24+ r#"
25+ name = "rename_users_table_to_customers"
4426
45− // Make sure inserts work using both the old and new name
46− old_db
47− .simple_query("INSERT INTO users(id) VALUES (1)")
48− .unwrap();
49− new_db
50− .simple_query("INSERT INTO customers(id) VALUES (2)")
51− .unwrap();
27+ [[actions]]
28+ type = "rename_table"
29+ table = "users"
30+ new_name = "customers"
31+ "#,
32+ );
5233
53− // Ensure the table can be queried using both the old and new name
54− let expected: Vec<i32> = vec![1, 2];
55− assert_eq!(
56− expected,
34+ test.intermediate(|old_db, new_db| {
35+ // Make sure inserts work using both the old and new name
5736 old_db
58− .query("SELECT id FROM users ORDER BY id", &[])
59− .unwrap()
60− .iter()
61− .map(|row| row.get::<_, i32>("id"))
62− .collect::<Vec<i32>>()
63− );
64− assert_eq!(
65− expected,
37+ .simple_query("INSERT INTO users(id) VALUES (1)")
38+ .unwrap();
6639 new_db
67− .query("SELECT id FROM customers ORDER BY id", &[])
68− .unwrap()
69− .iter()
70− .map(|row| row.get::<_, i32>("id"))
71− .collect::<Vec<i32>>()
72− );
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
48+ .query("SELECT id FROM users ORDER BY id", &[])
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()
59+ .iter()
60+ .map(|row| row.get::<_, i32>("id"))
61+ .collect::<Vec<i32>>()
62+ );
7363
74− // 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());
76− assert!(new_db.simple_query("SELECT id FROM users").is_err());
64+ // Ensure the table can't be queried using the wrong name for the schema
65+ assert!(old_db.simple_query("SELECT id FROM customers").is_err());
66+ assert!(new_db.simple_query("SELECT id FROM users").is_err());
67+ });
7768
78− reshape.complete().unwrap();
79− common::assert_cleaned_up(&mut new_db);
69+ test.run();
8070}