Switch State struct into enum

With the old fields in the `State` broken out we can now make `State` a
proper enum, replacing the nested `Status`. The naming with both `State`
and `Status` was a bit confusing.
This commit is contained in:
fabianlindfors 2022-01-14 00:10:20 +01:00
parent b1b79e26e5
commit 2d04d33b95
6 changed files with 51 additions and 78 deletions

View File

@ -15,7 +15,7 @@ pub mod migrations;
mod schema;
mod state;
pub use crate::state::{State, Status};
pub use crate::state::State;
pub struct Reshape {
pub state: State,
@ -46,7 +46,7 @@ impl Reshape {
fn new_with_config(config: &Config) -> anyhow::Result<Reshape> {
let mut db = DbConn::connect(config)?;
let state = State::load(&mut db);
let state = State::load(&mut db)?;
Ok(Reshape { db, state })
}
@ -55,15 +55,15 @@ impl Reshape {
where
T: IntoIterator<Item = Migration>,
{
self.state = State::load(&mut self.db);
self.state = State::load(&mut self.db)?;
// Make sure no migration is in progress
if let state::Status::InProgress { .. } = &self.state.status {
if let State::InProgress { .. } = &self.state {
println!("Migration already in progress, please complete using 'reshape complete'");
return Ok(());
}
if let state::Status::Completing { .. } = &self.state.status {
if let State::Completing { .. } = &self.state {
println!(
"Migration already in progress and has started completion, please finish using 'reshape complete'"
);
@ -83,9 +83,9 @@ impl Reshape {
// If we have already started applying some migrations we need to ensure that
// they are the same ones we want to apply now
if let state::Status::Applying {
if let State::Applying {
migrations: existing_migrations,
} = &self.state.status
} = &self.state
{
if existing_migrations != &remaining_migrations {
return Err(anyhow!(
@ -198,8 +198,8 @@ impl Reshape {
pub fn complete_migration(&mut self) -> anyhow::Result<()> {
// Make sure a migration is in progress
let (remaining_migrations, starting_migration_index, starting_action_index) = match self.state.status.clone() {
state::Status::InProgress { migrations } => {
let (remaining_migrations, starting_migration_index, starting_action_index) = match self.state.clone() {
State::InProgress { migrations } => {
// Move into the Completing state. Once in this state,
// the migration can't be aborted and must be completed.
self.state.completing(migrations.clone(), 0, 0);
@ -207,18 +207,18 @@ impl Reshape {
(migrations, 0, 0)
},
state::Status::Completing {
State::Completing {
migrations,
current_migration_index,
current_action_index
} => (migrations, current_migration_index, current_action_index),
state::Status::Aborting { .. } => {
State::Aborting { .. } => {
return Err(anyhow!("migration been aborted and can't be completed. Please finish using `reshape abort`."))
}
state::Status::Applying { .. } => {
State::Applying { .. } => {
return Err(anyhow!("a previous migration unexpectedly failed. Please run `reshape migrate` to try applying the migration again."))
}
state::Status::Idle => {
State::Idle => {
println!("No migration in progress");
return Ok(());
}
@ -396,7 +396,7 @@ impl Reshape {
))?;
}
if let Status::InProgress { migrations } = &self.state.status {
if let State::InProgress { migrations } = &self.state {
let target_migration = migrations.last().unwrap().name.to_string();
self.db.run(&format!(
"DROP SCHEMA IF EXISTS {} CASCADE",
@ -426,10 +426,9 @@ impl Reshape {
pub fn abort(&mut self) -> anyhow::Result<()> {
let (remaining_migrations, last_migration_index, last_action_index) = match self
.state
.status
.clone()
{
Status::InProgress { migrations } | Status::Applying { migrations } => {
State::InProgress { migrations } | State::Applying { migrations } => {
// Set to the Aborting state. Once this is done, the migration has to
// be fully aborted and can't be completed.
self.state.aborting(migrations.clone(), 0, 0);
@ -437,15 +436,15 @@ impl Reshape {
(migrations, usize::MAX, usize::MAX)
}
Status::Aborting {
State::Aborting {
migrations,
last_migration_index,
last_action_index,
} => (migrations, last_migration_index, last_action_index),
Status::Completing { .. } => {
State::Completing { .. } => {
return Err(anyhow!("Migration completion has already been started. Please run `reshape complete` again to finish it."));
}
Status::Idle => {
State::Idle => {
println!("No migration is in progress");
return Ok(());
}
@ -467,7 +466,7 @@ impl Reshape {
helpers::tear_down_helpers(&mut self.db).context("failed to tear down helpers")?;
self.state.status = state::Status::Idle;
self.state = State::Idle;
self.state
.save(&mut self.db)
.context("failed to save state")?;

View File

@ -4,14 +4,9 @@ use anyhow::anyhow;
use serde::{Deserialize, Serialize};
use version::version;
#[derive(Serialize, Deserialize, Debug)]
pub struct State {
pub status: Status,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(tag = "status")]
pub enum Status {
#[serde(tag = "state")]
pub enum State {
#[serde(rename = "idle")]
Idle,
@ -37,24 +32,23 @@ pub enum Status {
}
impl State {
pub fn load(db: &mut impl Conn) -> State {
Self::ensure_schema_and_table(db);
pub fn load(db: &mut impl Conn) -> anyhow::Result<State> {
Self::ensure_schema_and_table(db)?;
let results = db
.query("SELECT value FROM reshape.data WHERE key = 'state'")
.unwrap();
let results = db.query("SELECT value FROM reshape.data WHERE key = 'state'")?;
match results.first() {
let state = match results.first() {
Some(row) => {
let json: serde_json::Value = row.get(0);
serde_json::from_value(json).unwrap()
serde_json::from_value(json)?
}
None => Default::default(),
}
};
Ok(state)
}
pub fn save(&self, db: &mut impl Conn) -> anyhow::Result<()> {
Self::ensure_schema_and_table(db);
Self::ensure_schema_and_table(db)?;
let json = serde_json::to_value(self)?;
db.query_with_params(
@ -67,17 +61,17 @@ impl State {
pub fn clear(&mut self, db: &mut impl Conn) -> anyhow::Result<()> {
db.run("DROP SCHEMA reshape CASCADE")?;
let default = Self::default();
self.status = default.status;
*self = Self::default();
Ok(())
}
// Complete will change the status from Completing to Idle
// Complete will change the state from Completing to Idle
pub fn complete(&mut self, db: &mut impl Conn) -> anyhow::Result<()> {
let current_status = std::mem::replace(&mut self.status, Status::Idle);
match current_status {
Status::Completing { migrations, .. } => {
let current_state = std::mem::replace(self, Self::Idle);
match current_state {
Self::Completing { migrations, .. } => {
// Add migrations and update state in a transaction to ensure atomicity
let mut transaction = db.transaction()?;
save_migrations(&mut transaction, &migrations)?;
@ -85,8 +79,8 @@ impl State {
transaction.commit()?;
}
_ => {
// Move old status back
self.status = current_status;
// Move old state back
*self = current_state;
return Err(anyhow!(
"couldn't update state to be completed, not in Completing state"
@ -98,13 +92,13 @@ impl State {
}
pub fn applying(&mut self, new_migrations: Vec<Migration>) {
self.status = Status::Applying {
*self = Self::Applying {
migrations: new_migrations,
};
}
pub fn in_progress(&mut self, new_migrations: Vec<Migration>) {
self.status = Status::InProgress {
*self = Self::InProgress {
migrations: new_migrations,
};
}
@ -115,7 +109,7 @@ impl State {
current_migration_index: usize,
current_action_index: usize,
) {
self.status = Status::Completing {
*self = Self::Completing {
migrations,
current_migration_index,
current_action_index,
@ -128,20 +122,19 @@ impl State {
last_migration_index: usize,
last_action_index: usize,
) {
self.status = Status::Aborting {
*self = Self::Aborting {
migrations,
last_migration_index,
last_action_index,
}
}
fn ensure_schema_and_table(db: &mut impl Conn) {
db.run("CREATE SCHEMA IF NOT EXISTS reshape").unwrap();
fn ensure_schema_and_table(db: &mut impl Conn) -> anyhow::Result<()> {
db.run("CREATE SCHEMA IF NOT EXISTS reshape")?;
// Create data table which will be a key-value table containing
// the version and current state.
db.run("CREATE TABLE IF NOT EXISTS reshape.data (key TEXT PRIMARY KEY, value JSONB)")
.unwrap();
db.run("CREATE TABLE IF NOT EXISTS reshape.data (key TEXT PRIMARY KEY, value JSONB)")?;
// Create migrations table which will store all completed migrations
db.run(
@ -154,11 +147,10 @@ impl State {
completed_at TIMESTAMP DEFAULT NOW()
)
",
)
.unwrap();
)?;
// Update the current version
let encoded_version = serde_json::to_value(version!().to_string()).unwrap();
let encoded_version = serde_json::to_value(version!().to_string())?;
db.query_with_params(
"
INSERT INTO reshape.data (key, value)
@ -166,16 +158,15 @@ impl State {
ON CONFLICT (key) DO UPDATE SET value = $1
",
&[&encoded_version],
)
.unwrap();
)?;
Ok(())
}
}
impl Default for State {
fn default() -> Self {
State {
status: Status::Idle,
}
Self::Idle
}
}

View File

@ -1,5 +1,4 @@
use reshape::migrations::{AddColumn, Column, ColumnBuilder, CreateTableBuilder, Migration};
use reshape::Status;
mod common;
@ -59,7 +58,6 @@ fn add_column() {
// Run first migration, should automatically finish
reshape.migrate(first_migrations.clone()).unwrap();
assert!(matches!(reshape.state.status, Status::Idle));
// Update search paths
old_db
@ -151,7 +149,6 @@ fn add_column_nullable() {
// Run first migration, should automatically finish
reshape.migrate(first_migrations.clone()).unwrap();
assert!(matches!(reshape.state.status, Status::Idle));
// Update search paths
old_db
@ -246,7 +243,6 @@ fn add_column_with_default() {
// Run first migration, should automatically finish
reshape.migrate(first_migrations.clone()).unwrap();
assert!(matches!(reshape.state.status, Status::Idle));
// Update search paths
old_db

View File

@ -1,7 +1,6 @@
use reshape::migrations::{
AddIndex, AlterColumn, ColumnBuilder, ColumnChanges, CreateTableBuilder, Migration,
};
use reshape::Status;
mod common;
@ -46,7 +45,6 @@ fn alter_column_data() {
// Run first migration, should automatically finish
reshape.migrate(first_migrations.clone()).unwrap();
assert!(matches!(reshape.state.status, Status::Idle));
// Update search paths
old_db
@ -146,7 +144,6 @@ fn alter_column_set_not_null() {
// Run first migration, should automatically finish
reshape.migrate(first_migrations.clone()).unwrap();
assert!(matches!(reshape.state.status, Status::Idle));
// Update search paths
old_db
@ -247,7 +244,6 @@ fn alter_column_rename() {
// Run first migration, should automatically finish
reshape.migrate(first_migrations.clone()).unwrap();
assert!(matches!(reshape.state.status, Status::Idle));
// Update search paths
old_db
@ -343,7 +339,6 @@ fn alter_column_multiple() {
// Run first migration, should automatically finish
reshape.migrate(first_migrations.clone()).unwrap();
assert!(matches!(reshape.state.status, Status::Idle));
// Update search paths
old_db
@ -455,7 +450,6 @@ fn alter_column_default() {
// Run first migration, should automatically finish
reshape.migrate(first_migrations.clone()).unwrap();
assert!(matches!(reshape.state.status, Status::Idle));
// Update search paths
old_db
@ -564,7 +558,6 @@ fn alter_column_with_index() {
// Run first migration, should automatically finish
reshape.migrate(first_migrations.clone()).unwrap();
assert!(matches!(reshape.state.status, Status::Idle));
// Run second migration
reshape.migrate(second_migrations.clone()).unwrap();

View File

@ -1,7 +1,4 @@
use reshape::{
migrations::{ColumnBuilder, CreateTableBuilder, ForeignKey, Migration},
Status,
};
use reshape::migrations::{ColumnBuilder, CreateTableBuilder, ForeignKey, Migration};
mod common;
@ -40,7 +37,6 @@ fn create_table() {
reshape
.migrate(vec![create_table_migration.clone()])
.unwrap();
assert!(matches!(reshape.state.status, Status::Idle));
// Ensure table was created
let result = db

View File

@ -1,5 +1,4 @@
use reshape::migrations::{AddColumn, Column, CreateTable, Migration};
use reshape::Status;
mod common;
@ -45,7 +44,6 @@ fn invalid_migration() {
// Run first migration, should automatically finish
reshape.migrate(first_migrations.clone()).unwrap();
assert!(matches!(reshape.state.status, Status::Idle));
// Update search paths
old_db