on main help sign in
Switch `State` struct into enum
2d04d33fabianlindfors
src/lib.rs+19−20
@@ -15,7 +15,7 @@ pub mod migrations;
15mod schema;
15mod schema;
16mod state;
16mod state;
17
17
18−pub use crate::state::{State, Status};
18+pub use crate::state::State;
19
19
20pub struct Reshape {
20pub struct Reshape {
21 pub state: State,
21 pub state: State,
@@ -46,7 +46,7 @@ impl Reshape {
46
46
47 fn new_with_config(config: &Config) -> anyhow::Result<Reshape> {
47 fn new_with_config(config: &Config) -> anyhow::Result<Reshape> {
48 let mut db = DbConn::connect(config)?;
48 let mut db = DbConn::connect(config)?;
49− let state = State::load(&mut db);
49+ let state = State::load(&mut db)?;
50
50
51 Ok(Reshape { db, state })
51 Ok(Reshape { db, state })
52 }
52 }
@@ -55,15 +55,15 @@ impl Reshape {
55 where
55 where
56 T: IntoIterator<Item = Migration>,
56 T: IntoIterator<Item = Migration>,
57 {
57 {
58− self.state = State::load(&mut self.db);
58+ self.state = State::load(&mut self.db)?;
59
59
60 // Make sure no migration is in progress
60 // Make sure no migration is in progress
61− if let state::Status::InProgress { .. } = &self.state.status {
61+ if let State::InProgress { .. } = &self.state {
62 println!("Migration already in progress, please complete using 'reshape complete'");
62 println!("Migration already in progress, please complete using 'reshape complete'");
63 return Ok(());
63 return Ok(());
64 }
64 }
65
65
66− if let state::Status::Completing { .. } = &self.state.status {
66+ if let State::Completing { .. } = &self.state {
67 println!(
67 println!(
68 "Migration already in progress and has started completion, please finish using 'reshape complete'"
68 "Migration already in progress and has started completion, please finish using 'reshape complete'"
69 );
69 );
@@ -83,9 +83,9 @@ impl Reshape {
83
83
84 // If we have already started applying some migrations we need to ensure that
84 // If we have already started applying some migrations we need to ensure that
85 // they are the same ones we want to apply now
85 // they are the same ones we want to apply now
86− if let state::Status::Applying {
86+ if let State::Applying {
87 migrations: existing_migrations,
87 migrations: existing_migrations,
88− } = &self.state.status
88+ } = &self.state
89 {
89 {
90 if existing_migrations != &remaining_migrations {
90 if existing_migrations != &remaining_migrations {
91 return Err(anyhow!(
91 return Err(anyhow!(
@@ -198,8 +198,8 @@ impl Reshape {
198
198
199 pub fn complete_migration(&mut self) -> anyhow::Result<()> {
199 pub fn complete_migration(&mut self) -> anyhow::Result<()> {
200 // Make sure a migration is in progress
200 // Make sure a migration is in progress
201− let (remaining_migrations, starting_migration_index, starting_action_index) = match self.state.status.clone() {
201+ let (remaining_migrations, starting_migration_index, starting_action_index) = match self.state.clone() {
202− state::Status::InProgress { migrations } => {
202+ State::InProgress { migrations } => {
203 // Move into the Completing state. Once in this state,
203 // Move into the Completing state. Once in this state,
204 // the migration can't be aborted and must be completed.
204 // the migration can't be aborted and must be completed.
205 self.state.completing(migrations.clone(), 0, 0);
205 self.state.completing(migrations.clone(), 0, 0);
@@ -207,18 +207,18 @@ impl Reshape {
207
207
208 (migrations, 0, 0)
208 (migrations, 0, 0)
209 },
209 },
210− state::Status::Completing {
210+ State::Completing {
211 migrations,
211 migrations,
212 current_migration_index,
212 current_migration_index,
213 current_action_index
213 current_action_index
214 } => (migrations, current_migration_index, current_action_index),
214 } => (migrations, current_migration_index, current_action_index),
215− state::Status::Aborting { .. } => {
215+ State::Aborting { .. } => {
216 return Err(anyhow!("migration been aborted and can't be completed. Please finish using `reshape abort`."))
216 return Err(anyhow!("migration been aborted and can't be completed. Please finish using `reshape abort`."))
217 }
217 }
218− state::Status::Applying { .. } => {
218+ State::Applying { .. } => {
219 return Err(anyhow!("a previous migration unexpectedly failed. Please run `reshape migrate` to try applying the migration again."))
219 return Err(anyhow!("a previous migration unexpectedly failed. Please run `reshape migrate` to try applying the migration again."))
220 }
220 }
221− state::Status::Idle => {
221+ State::Idle => {
222 println!("No migration in progress");
222 println!("No migration in progress");
223 return Ok(());
223 return Ok(());
224 }
224 }
@@ -396,7 +396,7 @@ impl Reshape {
396 ))?;
396 ))?;
397 }
397 }
398
398
399− if let Status::InProgress { migrations } = &self.state.status {
399+ if let State::InProgress { migrations } = &self.state {
400 let target_migration = migrations.last().unwrap().name.to_string();
400 let target_migration = migrations.last().unwrap().name.to_string();
401 self.db.run(&format!(
401 self.db.run(&format!(
402 "DROP SCHEMA IF EXISTS {} CASCADE",
402 "DROP SCHEMA IF EXISTS {} CASCADE",
@@ -426,10 +426,9 @@ impl Reshape {
426 pub fn abort(&mut self) -> anyhow::Result<()> {
426 pub fn abort(&mut self) -> anyhow::Result<()> {
427 let (remaining_migrations, last_migration_index, last_action_index) = match self
427 let (remaining_migrations, last_migration_index, last_action_index) = match self
428 .state
428 .state
429− .status
430 .clone()
429 .clone()
431 {
430 {
432− Status::InProgress { migrations } | Status::Applying { migrations } => {
431+ State::InProgress { migrations } | State::Applying { migrations } => {
433 // Set to the Aborting state. Once this is done, the migration has to
432 // Set to the Aborting state. Once this is done, the migration has to
434 // be fully aborted and can't be completed.
433 // be fully aborted and can't be completed.
435 self.state.aborting(migrations.clone(), 0, 0);
434 self.state.aborting(migrations.clone(), 0, 0);
@@ -437,15 +436,15 @@ impl Reshape {
437
436
438 (migrations, usize::MAX, usize::MAX)
437 (migrations, usize::MAX, usize::MAX)
439 }
438 }
440− Status::Aborting {
439+ State::Aborting {
441 migrations,
440 migrations,
442 last_migration_index,
441 last_migration_index,
443 last_action_index,
442 last_action_index,
444 } => (migrations, last_migration_index, last_action_index),
443 } => (migrations, last_migration_index, last_action_index),
445− Status::Completing { .. } => {
444+ State::Completing { .. } => {
446 return Err(anyhow!("Migration completion has already been started. Please run `reshape complete` again to finish it."));
445 return Err(anyhow!("Migration completion has already been started. Please run `reshape complete` again to finish it."));
447 }
446 }
448− Status::Idle => {
447+ State::Idle => {
449 println!("No migration is in progress");
448 println!("No migration is in progress");
450 return Ok(());
449 return Ok(());
451 }
450 }
@@ -467,7 +466,7 @@ impl Reshape {
467
466
468 helpers::tear_down_helpers(&mut self.db).context("failed to tear down helpers")?;
467 helpers::tear_down_helpers(&mut self.db).context("failed to tear down helpers")?;
469
468
470− self.state.status = state::Status::Idle;
469+ self.state = State::Idle;
471 self.state
470 self.state
472 .save(&mut self.db)
471 .save(&mut self.db)
473 .context("failed to save state")?;
472 .context("failed to save state")?;
src/state.rs+31−40
@@ -4,14 +4,9 @@ use anyhow::anyhow;
4use serde::{Deserialize, Serialize};
4use serde::{Deserialize, Serialize};
5use version::version;
5use version::version;
6
6
7−#[derive(Serialize, Deserialize, Debug)]
8−pub struct State {
9− pub status: Status,
10−}
11−
12#[derive(Serialize, Deserialize, Clone, Debug)]
7#[derive(Serialize, Deserialize, Clone, Debug)]
13−#[serde(tag = "status")]
8+#[serde(tag = "state")]
14−pub enum Status {
9+pub enum State {
15 #[serde(rename = "idle")]
10 #[serde(rename = "idle")]
16 Idle,
11 Idle,
17
12
@@ -37,24 +32,23 @@ pub enum Status {
37}
32}
38
33
39impl State {
34impl State {
40− pub fn load(db: &mut impl Conn) -> State {
35+ pub fn load(db: &mut impl Conn) -> anyhow::Result<State> {
41− Self::ensure_schema_and_table(db);
36+ Self::ensure_schema_and_table(db)?;
42
37
43− let results = db
44− .query("SELECT value FROM reshape.data WHERE key = 'state'")
38+ let results = db.query("SELECT value FROM reshape.data WHERE key = 'state'")?;
45− .unwrap();
46
39
47− match results.first() {
40+ let state = match results.first() {
48 Some(row) => {
41 Some(row) => {
49 let json: serde_json::Value = row.get(0);
42 let json: serde_json::Value = row.get(0);
50− serde_json::from_value(json).unwrap()
43+ serde_json::from_value(json)?
51 }
44 }
52 None => Default::default(),
45 None => Default::default(),
53− }
46+ };
47+ Ok(state)
54 }
48 }
55
49
56 pub fn save(&self, db: &mut impl Conn) -> anyhow::Result<()> {
50 pub fn save(&self, db: &mut impl Conn) -> anyhow::Result<()> {
57− Self::ensure_schema_and_table(db);
51+ Self::ensure_schema_and_table(db)?;
58
52
59 let json = serde_json::to_value(self)?;
53 let json = serde_json::to_value(self)?;
60 db.query_with_params(
54 db.query_with_params(
@@ -67,17 +61,17 @@ impl State {
67 pub fn clear(&mut self, db: &mut impl Conn) -> anyhow::Result<()> {
61 pub fn clear(&mut self, db: &mut impl Conn) -> anyhow::Result<()> {
68 db.run("DROP SCHEMA reshape CASCADE")?;
62 db.run("DROP SCHEMA reshape CASCADE")?;
69
63
70− let default = Self::default();
64+ *self = Self::default();
71− self.status = default.status;
72
65
73 Ok(())
66 Ok(())
74 }
67 }
75
68
76− // Complete will change the status from Completing to Idle
69+ // Complete will change the state from Completing to Idle
77 pub fn complete(&mut self, db: &mut impl Conn) -> anyhow::Result<()> {
70 pub fn complete(&mut self, db: &mut impl Conn) -> anyhow::Result<()> {
78− let current_status = std::mem::replace(&mut self.status, Status::Idle);
71+ let current_state = std::mem::replace(self, Self::Idle);
79− match current_status {
72+
73+ match current_state {
80− Status::Completing { migrations, .. } => {
74+ Self::Completing { migrations, .. } => {
81 // Add migrations and update state in a transaction to ensure atomicity
75 // Add migrations and update state in a transaction to ensure atomicity
82 let mut transaction = db.transaction()?;
76 let mut transaction = db.transaction()?;
83 save_migrations(&mut transaction, &migrations)?;
77 save_migrations(&mut transaction, &migrations)?;
@@ -85,8 +79,8 @@ impl State {
85 transaction.commit()?;
79 transaction.commit()?;
86 }
80 }
87 _ => {
81 _ => {
88− // Move old status back
82+ // Move old state back
89− self.status = current_status;
83+ *self = current_state;
90
84
91 return Err(anyhow!(
85 return Err(anyhow!(
92 "couldn't update state to be completed, not in Completing state"
86 "couldn't update state to be completed, not in Completing state"
@@ -98,13 +92,13 @@ impl State {
98 }
92 }
99
93
100 pub fn applying(&mut self, new_migrations: Vec<Migration>) {
94 pub fn applying(&mut self, new_migrations: Vec<Migration>) {
101− self.status = Status::Applying {
95+ *self = Self::Applying {
102 migrations: new_migrations,
96 migrations: new_migrations,
103 };
97 };
104 }
98 }
105
99
106 pub fn in_progress(&mut self, new_migrations: Vec<Migration>) {
100 pub fn in_progress(&mut self, new_migrations: Vec<Migration>) {
107− self.status = Status::InProgress {
101+ *self = Self::InProgress {
108 migrations: new_migrations,
102 migrations: new_migrations,
109 };
103 };
110 }
104 }
@@ -115,7 +109,7 @@ impl State {
115 current_migration_index: usize,
109 current_migration_index: usize,
116 current_action_index: usize,
110 current_action_index: usize,
117 ) {
111 ) {
118− self.status = Status::Completing {
112+ *self = Self::Completing {
119 migrations,
113 migrations,
120 current_migration_index,
114 current_migration_index,
121 current_action_index,
115 current_action_index,
@@ -128,20 +122,19 @@ impl State {
128 last_migration_index: usize,
122 last_migration_index: usize,
129 last_action_index: usize,
123 last_action_index: usize,
130 ) {
124 ) {
131− self.status = Status::Aborting {
125+ *self = Self::Aborting {
132 migrations,
126 migrations,
133 last_migration_index,
127 last_migration_index,
134 last_action_index,
128 last_action_index,
135 }
129 }
136 }
130 }
137
131
138− fn ensure_schema_and_table(db: &mut impl Conn) {
132+ fn ensure_schema_and_table(db: &mut impl Conn) -> anyhow::Result<()> {
139− db.run("CREATE SCHEMA IF NOT EXISTS reshape").unwrap();
133+ db.run("CREATE SCHEMA IF NOT EXISTS reshape")?;
140
134
141 // Create data table which will be a key-value table containing
135 // Create data table which will be a key-value table containing
142 // the version and current state.
136 // the version and current state.
143− db.run("CREATE TABLE IF NOT EXISTS reshape.data (key TEXT PRIMARY KEY, value JSONB)")
137+ db.run("CREATE TABLE IF NOT EXISTS reshape.data (key TEXT PRIMARY KEY, value JSONB)")?;
144− .unwrap();
145
138
146 // Create migrations table which will store all completed migrations
139 // Create migrations table which will store all completed migrations
147 db.run(
140 db.run(
@@ -154,11 +147,10 @@ impl State {
154 completed_at TIMESTAMP DEFAULT NOW()
147 completed_at TIMESTAMP DEFAULT NOW()
155 )
148 )
156 ",
149 ",
157− )
150+ )?;
158− .unwrap();
159
151
160 // Update the current version
152 // Update the current version
161− let encoded_version = serde_json::to_value(version!().to_string()).unwrap();
153+ let encoded_version = serde_json::to_value(version!().to_string())?;
162 db.query_with_params(
154 db.query_with_params(
163 "
155 "
164 INSERT INTO reshape.data (key, value)
156 INSERT INTO reshape.data (key, value)
@@ -166,16 +158,15 @@ impl State {
166 ON CONFLICT (key) DO UPDATE SET value = $1
158 ON CONFLICT (key) DO UPDATE SET value = $1
167 ",
159 ",
168 &[&encoded_version],
160 &[&encoded_version],
169− )
161+ )?;
170− .unwrap();
162+
163+ Ok(())
171 }
164 }
172}
165}
173
166
174impl Default for State {
167impl Default for State {
175 fn default() -> Self {
168 fn default() -> Self {
176− State {
169+ Self::Idle
177− status: Status::Idle,
178− }
179 }
170 }
180}
171}
181
172
tests/add_column.rs−4
@@ -1,5 +1,4 @@
1use reshape::migrations::{AddColumn, Column, ColumnBuilder, CreateTableBuilder, Migration};
1use reshape::migrations::{AddColumn, Column, ColumnBuilder, CreateTableBuilder, Migration};
2−use reshape::Status;
3
2
4mod common;
3mod common;
5
4
@@ -59,7 +58,6 @@ fn add_column() {
59
58
60 // Run first migration, should automatically finish
59 // Run first migration, should automatically finish
61 reshape.migrate(first_migrations.clone()).unwrap();
60 reshape.migrate(first_migrations.clone()).unwrap();
62− assert!(matches!(reshape.state.status, Status::Idle));
63
61
64 // Update search paths
62 // Update search paths
65 old_db
63 old_db
@@ -151,7 +149,6 @@ fn add_column_nullable() {
151
149
152 // Run first migration, should automatically finish
150 // Run first migration, should automatically finish
153 reshape.migrate(first_migrations.clone()).unwrap();
151 reshape.migrate(first_migrations.clone()).unwrap();
154− assert!(matches!(reshape.state.status, Status::Idle));
155
152
156 // Update search paths
153 // Update search paths
157 old_db
154 old_db
@@ -246,7 +243,6 @@ fn add_column_with_default() {
246
243
247 // Run first migration, should automatically finish
244 // Run first migration, should automatically finish
248 reshape.migrate(first_migrations.clone()).unwrap();
245 reshape.migrate(first_migrations.clone()).unwrap();
249− assert!(matches!(reshape.state.status, Status::Idle));
250
246
251 // Update search paths
247 // Update search paths
252 old_db
248 old_db
tests/alter_column.rs−7
@@ -1,7 +1,6 @@
1use reshape::migrations::{
1use reshape::migrations::{
2 AddIndex, AlterColumn, ColumnBuilder, ColumnChanges, CreateTableBuilder, Migration,
2 AddIndex, AlterColumn, ColumnBuilder, ColumnChanges, CreateTableBuilder, Migration,
3};
3};
4−use reshape::Status;
5
4
6mod common;
5mod common;
7
6
@@ -46,7 +45,6 @@ fn alter_column_data() {
46
45
47 // Run first migration, should automatically finish
46 // Run first migration, should automatically finish
48 reshape.migrate(first_migrations.clone()).unwrap();
47 reshape.migrate(first_migrations.clone()).unwrap();
49− assert!(matches!(reshape.state.status, Status::Idle));
50
48
51 // Update search paths
49 // Update search paths
52 old_db
50 old_db
@@ -146,7 +144,6 @@ fn alter_column_set_not_null() {
146
144
147 // Run first migration, should automatically finish
145 // Run first migration, should automatically finish
148 reshape.migrate(first_migrations.clone()).unwrap();
146 reshape.migrate(first_migrations.clone()).unwrap();
149− assert!(matches!(reshape.state.status, Status::Idle));
150
147
151 // Update search paths
148 // Update search paths
152 old_db
149 old_db
@@ -247,7 +244,6 @@ fn alter_column_rename() {
247
244
248 // Run first migration, should automatically finish
245 // Run first migration, should automatically finish
249 reshape.migrate(first_migrations.clone()).unwrap();
246 reshape.migrate(first_migrations.clone()).unwrap();
250− assert!(matches!(reshape.state.status, Status::Idle));
251
247
252 // Update search paths
248 // Update search paths
253 old_db
249 old_db
@@ -343,7 +339,6 @@ fn alter_column_multiple() {
343
339
344 // Run first migration, should automatically finish
340 // Run first migration, should automatically finish
345 reshape.migrate(first_migrations.clone()).unwrap();
341 reshape.migrate(first_migrations.clone()).unwrap();
346− assert!(matches!(reshape.state.status, Status::Idle));
347
342
348 // Update search paths
343 // Update search paths
349 old_db
344 old_db
@@ -455,7 +450,6 @@ fn alter_column_default() {
455
450
456 // Run first migration, should automatically finish
451 // Run first migration, should automatically finish
457 reshape.migrate(first_migrations.clone()).unwrap();
452 reshape.migrate(first_migrations.clone()).unwrap();
458− assert!(matches!(reshape.state.status, Status::Idle));
459
453
460 // Update search paths
454 // Update search paths
461 old_db
455 old_db
@@ -564,7 +558,6 @@ fn alter_column_with_index() {
564
558
565 // Run first migration, should automatically finish
559 // Run first migration, should automatically finish
566 reshape.migrate(first_migrations.clone()).unwrap();
560 reshape.migrate(first_migrations.clone()).unwrap();
567− assert!(matches!(reshape.state.status, Status::Idle));
568
561
569 // Run second migration
562 // Run second migration
570 reshape.migrate(second_migrations.clone()).unwrap();
563 reshape.migrate(second_migrations.clone()).unwrap();
tests/create_table.rs+1−5
@@ -1,7 +1,4 @@
1−use reshape::{
2− migrations::{ColumnBuilder, CreateTableBuilder, ForeignKey, Migration},
1+use reshape::migrations::{ColumnBuilder, CreateTableBuilder, ForeignKey, Migration};
3− Status,
4−};
5
2
6mod common;
3mod common;
7
4
@@ -40,7 +37,6 @@ fn create_table() {
40 reshape
37 reshape
41 .migrate(vec![create_table_migration.clone()])
38 .migrate(vec![create_table_migration.clone()])
42 .unwrap();
39 .unwrap();
43− assert!(matches!(reshape.state.status, Status::Idle));
44
40
45 // Ensure table was created
41 // Ensure table was created
46 let result = db
42 let result = db
tests/failure.rs−2
@@ -1,5 +1,4 @@
1use reshape::migrations::{AddColumn, Column, CreateTable, Migration};
1use reshape::migrations::{AddColumn, Column, CreateTable, Migration};
2−use reshape::Status;
3
2
4mod common;
3mod common;
5
4
@@ -45,7 +44,6 @@ fn invalid_migration() {
45
44
46 // Run first migration, should automatically finish
45 // Run first migration, should automatically finish
47 reshape.migrate(first_migrations.clone()).unwrap();
46 reshape.migrate(first_migrations.clone()).unwrap();
48− assert!(matches!(reshape.state.status, Status::Idle));
49
47
50 // Update search paths
48 // Update search paths
51 old_db
49 old_db