Add context to errors

This commit is contained in:
fabianlindfors 2022-01-03 00:03:23 +01:00
parent 6e005144f4
commit 0bac1245cd
9 changed files with 140 additions and 69 deletions

View File

@ -2,7 +2,7 @@ use anyhow::Context;
use crate::db::Conn; use crate::db::Conn;
pub fn setup_helpers(db: &mut dyn Conn, current_migration: &Option<String>) -> anyhow::Result<()> { pub fn set_up_helpers(db: &mut dyn Conn, current_migration: &Option<String>) -> anyhow::Result<()> {
let predicate = if let Some(current_migration) = current_migration { let predicate = if let Some(current_migration) = current_migration {
format!( format!(
"current_setting('search_path') = 'migration_{}' OR setting_bool", "current_setting('search_path') = 'migration_{}' OR setting_bool",
@ -32,7 +32,7 @@ pub fn setup_helpers(db: &mut dyn Conn, current_migration: &Option<String>) -> a
Ok(()) Ok(())
} }
pub fn teardown_helpers(db: &mut dyn Conn) -> anyhow::Result<()> { pub fn tear_down_helpers(db: &mut dyn Conn) -> anyhow::Result<()> {
db.query("DROP FUNCTION reshape.is_old_schema;")?; db.query("DROP FUNCTION reshape.is_old_schema;")?;
Ok(()) Ok(())
} }

View File

@ -3,7 +3,7 @@ use crate::{
schema::Schema, schema::Schema,
}; };
use anyhow::anyhow; use anyhow::{anyhow, Context};
use colored::*; use colored::*;
use db::{Conn, DbConn}; use db::{Conn, DbConn};
use postgres::Config; use postgres::Config;
@ -96,7 +96,8 @@ impl Reshape {
println!(" Applying {} migrations\n", remaining_migrations.len()); println!(" Applying {} migrations\n", remaining_migrations.len());
helpers::setup_helpers(&mut self.db, current_migration)?; helpers::set_up_helpers(&mut self.db, current_migration)
.context("failed to set up helpers")?;
let mut new_schema = Schema::new(); let mut new_schema = Schema::new();
let mut processed_migrations: &[Migration] = &[]; let mut processed_migrations: &[Migration] = &[];
@ -107,10 +108,13 @@ impl Reshape {
processed_migrations = &remaining_migrations[..migration_index + 1]; processed_migrations = &remaining_migrations[..migration_index + 1];
for (action_index, action) in migration.actions.iter().enumerate() { for (action_index, action) in migration.actions.iter().enumerate() {
print!(" + {} ", action.describe()); let description = action.describe();
print!(" + {} ", description);
let ctx = MigrationContext::new(migration_index, action_index); let ctx = MigrationContext::new(migration_index, action_index);
result = action.run(&ctx, &mut self.db, &new_schema); result = action
.run(&ctx, &mut self.db, &new_schema)
.with_context(|| format!("failed to {}", description));
if result.is_ok() { if result.is_ok() {
action.update_schema(&ctx, &mut new_schema); action.update_schema(&ctx, &mut new_schema);
@ -133,16 +137,22 @@ impl Reshape {
// Create schema and views for migration // Create schema and views for migration
let target_migration = remaining_migrations.last().unwrap().name.to_string(); let target_migration = remaining_migrations.last().unwrap().name.to_string();
self.create_schema_for_migration(&target_migration, &new_schema)?; self.create_schema_for_migration(&target_migration, &new_schema)
.with_context(|| {
format!("failed to create schema for migration {}", target_migration)
})?;
// Update state once migrations have been performed // Update state once migrations have been performed
self.state.in_progress(remaining_migrations); self.state.in_progress(remaining_migrations);
self.state.save(&mut self.db)?; self.state
.save(&mut self.db)
.context("failed to save in-progress state")?;
// If we started from a blank slate, we can finish the migration immediately // If we started from a blank slate, we can finish the migration immediately
if current_migration.is_none() { if current_migration.is_none() {
println!("Automatically completing migrations\n"); println!("Automatically completing migrations\n");
self.complete_migration()?; self.complete_migration()
.context("failed to automatically complete migrations")?;
println!("Migrations complete:"); println!("Migrations complete:");
println!( println!(
@ -176,41 +186,61 @@ impl Reshape {
} }
}; };
helpers::teardown_helpers(&mut self.db)?; helpers::tear_down_helpers(&mut self.db).context("failed to tear down helpers")?;
let mut temp_schema = Schema::new(); let mut temp_schema = Schema::new();
// Run all the completion changes as a transaction to avoid incomplete updates // Run all the completion changes as a transaction to avoid incomplete updates
let mut transaction = self.db.transaction()?; let mut transaction = self
.db
.transaction()
.context("failed to start transaction")?;
// Remove previous migration's schema // Remove previous migration's schema
if let Some(current_migration) = &self.state.current_migration { if let Some(current_migration) = &self.state.current_migration {
transaction.run(&format!( transaction
"DROP SCHEMA IF EXISTS {} CASCADE", .run(&format!(
schema_name_for_migration(current_migration) "DROP SCHEMA IF EXISTS {} CASCADE",
))?; schema_name_for_migration(current_migration)
))
.context("failed to remove previous migration's schema")?;
} }
for (migration_index, migration) in remaining_migrations.iter().enumerate() { for (migration_index, migration) in remaining_migrations.iter().enumerate() {
println!("Completing '{}':", migration.name); println!("Completing '{}':", migration.name);
for (action_index, action) in migration.actions.iter().enumerate() { for (action_index, action) in migration.actions.iter().enumerate() {
print!(" + {} ", action.describe()); let description = action.describe();
print!(" + {} ", description);
let ctx = MigrationContext::new(migration_index, action_index); let ctx = MigrationContext::new(migration_index, action_index);
action.complete(&ctx, &mut transaction, &temp_schema)?; let result = action
action.update_schema(&ctx, &mut temp_schema); .complete(&ctx, &mut transaction, &temp_schema)
.with_context(|| format!("failed to complete migration {}", migration.name))
.with_context(|| format!("failed to complete action: {}", description));
println!("{}", "done".green()); if result.is_ok() {
action.update_schema(&ctx, &mut temp_schema);
println!("{}", "done".green());
} else {
println!("{}", "failed".green());
return result;
}
} }
println!(""); println!("");
} }
self.state.complete()?; self.state
self.state.save(&mut transaction)?; .complete()
.context("failed to update state as completed")?;
self.state
.save(&mut transaction)
.context("failed to save state after setting as completed")?;
transaction.commit()?; transaction
.commit()
.context("failed to apply transaction")?;
Ok(()) Ok(())
} }
@ -223,7 +253,13 @@ impl Reshape {
// Create schema for migration // Create schema for migration
let schema_name = schema_name_for_migration(migration_name); let schema_name = schema_name_for_migration(migration_name);
self.db self.db
.run(&format!("CREATE SCHEMA IF NOT EXISTS {}", schema_name))?; .run(&format!("CREATE SCHEMA IF NOT EXISTS {}", schema_name))
.with_context(|| {
format!(
"failed to create schema {} for migration {}",
schema_name, migration_name
)
})?;
// Create views inside schema // Create views inside schema
for table in schema.get_tables(&mut self.db)? { for table in schema.get_tables(&mut self.db)? {
@ -252,7 +288,8 @@ impl Reshape {
table_name = table.real_name, table_name = table.real_name,
view_name = table.name, view_name = table.name,
columns = select_columns.join(","), columns = select_columns.join(","),
))?; ))
.with_context(|| format!("failed to create view for table {}", table.name))?;
Ok(()) Ok(())
} }
@ -300,16 +337,19 @@ impl Reshape {
}; };
let target_migration = remaining_migrations.last().unwrap().name.to_string(); let target_migration = remaining_migrations.last().unwrap().name.to_string();
helpers::teardown_helpers(&mut self.db)?; helpers::tear_down_helpers(&mut self.db).context("failed to tear down helpers")?;
// Run all the abort changes as a transaction to avoid incomplete changes // Run all the abort changes as a transaction to avoid incomplete changes
let mut transaction = self.db.transaction()?; let mut transaction = self
.db
.transaction()
.context("failed to start transaction")?;
// Remove new migration's schema // Remove new migration's schema
transaction.run(&format!( let schema_name = schema_name_for_migration(&target_migration);
"DROP SCHEMA IF EXISTS {} CASCADE", transaction
schema_name_for_migration(&target_migration) .run(&format!("DROP SCHEMA IF EXISTS {} CASCADE", schema_name,))
))?; .with_context(|| format!("failed to drop schema {}", schema_name))?;
// Abort all pending migrations // Abort all pending migrations
abort_migrations(&mut transaction, &remaining_migrations)?; abort_migrations(&mut transaction, &remaining_migrations)?;
@ -317,9 +357,13 @@ impl Reshape {
let keep_count = self.state.migrations.len() - remaining_migrations.len(); let keep_count = self.state.migrations.len() - remaining_migrations.len();
self.state.migrations.truncate(keep_count); self.state.migrations.truncate(keep_count);
self.state.status = state::Status::Idle; self.state.status = state::Status::Idle;
self.state.save(&mut transaction)?; self.state
.save(&mut transaction)
.context("failed to save state")?;
transaction.commit()?; transaction
.commit()
.context("failed to commit transaction")?;
Ok(()) Ok(())
} }
@ -331,7 +375,10 @@ fn abort_migrations(db: &mut dyn Conn, migrations: &[Migration]) -> anyhow::Resu
print!("Aborting '{}' ", migration.name); print!("Aborting '{}' ", migration.name);
for (action_index, action) in migration.actions.iter().rev().enumerate() { for (action_index, action) in migration.actions.iter().rev().enumerate() {
let ctx = MigrationContext::new(migration_index, action_index); let ctx = MigrationContext::new(migration_index, action_index);
action.abort(&ctx, db)?; action
.abort(&ctx, db)
.with_context(|| format!("failed to abort migration {}", migration.name))
.with_context(|| format!("failed to abort action: {}", action.describe()))?;
} }
println!("{}", "done".green()); println!("{}", "done".green());
} }

View File

@ -1,5 +1,6 @@
use super::{common, Action, Column, MigrationContext}; use super::{common, Action, Column, MigrationContext};
use crate::{db::Conn, schema::Schema}; use crate::{db::Conn, schema::Schema};
use anyhow::Context;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
@ -80,7 +81,7 @@ impl Action for AddColumn {
table = self.table, table = self.table,
definition = definition_parts.join(" "), definition = definition_parts.join(" "),
); );
db.run(&query)?; db.run(&query).context("failed to add column")?;
if let Some(up) = &self.up { if let Some(up) = &self.up {
let table = schema.get_table(db, &self.table)?; let table = schema.get_table(db, &self.table)?;
@ -124,12 +125,13 @@ impl Action for AddColumn {
table = self.table, table = self.table,
declarations = declarations.join("\n"), declarations = declarations.join("\n"),
); );
db.run(&query)?; db.run(&query).context("failed to create up trigger")?;
} }
// Backfill values in batches // Backfill values in batches
if self.up.is_some() { if self.up.is_some() {
common::batch_touch_rows(db, &table.real_name, &temp_column_name)?; common::batch_touch_rows(db, &table.real_name, &temp_column_name)
.context("failed to batch update existing rows")?;
} }
// Add a temporary NOT NULL constraint if the column shouldn't be nullable. // Add a temporary NOT NULL constraint if the column shouldn't be nullable.
@ -147,7 +149,8 @@ impl Action for AddColumn {
constraint_name = self.not_null_constraint_name(&ctx), constraint_name = self.not_null_constraint_name(&ctx),
column = temp_column_name, column = temp_column_name,
); );
db.run(&query)?; db.run(&query)
.context("failed to add NOT NULL constraint")?;
} }
Ok(()) Ok(())
@ -170,7 +173,7 @@ impl Action for AddColumn {
table = self.table, table = self.table,
trigger_name = self.trigger_name(ctx), trigger_name = self.trigger_name(ctx),
); );
db.run(&query)?; db.run(&query).context("failed to drop up trigger")?;
// Update column to be NOT NULL if necessary // Update column to be NOT NULL if necessary
if !self.column.nullable { if !self.column.nullable {
@ -184,7 +187,8 @@ impl Action for AddColumn {
table = self.table, table = self.table,
constraint_name = self.not_null_constraint_name(ctx), constraint_name = self.not_null_constraint_name(ctx),
); );
db.run(&query)?; db.run(&query)
.context("failed to validate NOT NULL constraint")?;
// Update the column to be NOT NULL. // Update the column to be NOT NULL.
// This requires an exclusive lock but since PG 12 it can check // This requires an exclusive lock but since PG 12 it can check
@ -198,7 +202,7 @@ impl Action for AddColumn {
table = self.table, table = self.table,
column = self.temp_column_name(ctx), column = self.temp_column_name(ctx),
); );
db.run(&query)?; db.run(&query).context("failed to set column as NOT NULL")?;
// Drop the temporary constraint // Drop the temporary constraint
let query = format!( let query = format!(
@ -209,7 +213,8 @@ impl Action for AddColumn {
table = self.table, table = self.table,
constraint_name = self.not_null_constraint_name(ctx), constraint_name = self.not_null_constraint_name(ctx),
); );
db.run(&query)?; db.run(&query)
.context("failed to drop NOT NULL constraint")?;
} }
// Rename the temporary column to its real name // Rename the temporary column to its real name
@ -221,7 +226,8 @@ impl Action for AddColumn {
table = table.real_name, table = table.real_name,
temp_column_name = self.temp_column_name(ctx), temp_column_name = self.temp_column_name(ctx),
column_name = self.column.name, column_name = self.column.name,
))?; ))
.context("failed to rename column to final name")?;
Ok(()) Ok(())
} }
@ -244,7 +250,7 @@ impl Action for AddColumn {
table = self.table, table = self.table,
trigger_name = self.trigger_name(ctx), trigger_name = self.trigger_name(ctx),
); );
db.run(&query)?; db.run(&query).context("failed to drop up trigger")?;
// Remove column // Remove column
let query = format!( let query = format!(
@ -255,7 +261,7 @@ impl Action for AddColumn {
table = self.table, table = self.table,
column = self.temp_column_name(ctx), column = self.temp_column_name(ctx),
); );
db.run(&query)?; db.run(&query).context("failed to drop column")?;
Ok(()) Ok(())
} }

View File

@ -1,5 +1,6 @@
use super::{Action, MigrationContext}; use super::{Action, MigrationContext};
use crate::{db::Conn, schema::Schema}; use crate::{db::Conn, schema::Schema};
use anyhow::Context;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
@ -37,7 +38,8 @@ impl Action for AddIndex {
name = self.name, name = self.name,
table = self.table, table = self.table,
columns = column_real_names.join(", "), columns = column_real_names.join(", "),
))?; ))
.context("failed to create index")?;
Ok(()) Ok(())
} }
@ -59,7 +61,8 @@ impl Action for AddIndex {
", ",
name = self.name, name = self.name,
table = self.table, table = self.table,
))?; ))
.context("failed to drop index")?;
Ok(()) Ok(())
} }
} }

View File

@ -1,6 +1,6 @@
use super::{Action, MigrationContext}; use super::{Action, MigrationContext};
use crate::{db::Conn, migrations::common, schema::Schema}; use crate::{db::Conn, migrations::common, schema::Schema};
use anyhow::anyhow; use anyhow::{anyhow, Context};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
@ -90,7 +90,7 @@ impl Action for AlterColumn {
.columns .columns
.iter() .iter()
.find(|column| column.name == self.column) .find(|column| column.name == self.column)
.ok_or_else(|| anyhow!("no such column exists"))?; .ok_or_else(|| anyhow!("no such column {} exists", self.column))?;
let temporary_column_type = self let temporary_column_type = self
.changes .changes
@ -108,7 +108,7 @@ impl Action for AlterColumn {
temp_column = self.temporary_column_name(ctx), temp_column = self.temporary_column_name(ctx),
temp_column_type = temporary_column_type, temp_column_type = temporary_column_type,
); );
db.run(&query)?; db.run(&query).context("failed to add temporary column")?;
let declarations: Vec<String> = table let declarations: Vec<String> = table
.columns .columns
@ -173,10 +173,12 @@ impl Action for AlterColumn {
down_trigger = self.down_trigger_name(ctx), down_trigger = self.down_trigger_name(ctx),
declarations = declarations.join("\n"), declarations = declarations.join("\n"),
); );
db.run(&query)?; db.run(&query)
.context("failed to create up and down triggers")?;
// Backfill values in batches by touching the previous column // Backfill values in batches by touching the previous column
common::batch_touch_rows(db, &table.real_name, &column.real_name)?; common::batch_touch_rows(db, &table.real_name, &column.real_name)
.context("failed to batch update existing rows")?;
// Add a temporary NOT NULL constraint if the column shouldn't be nullable. // Add a temporary NOT NULL constraint if the column shouldn't be nullable.
// This constraint is set as NOT VALID so it doesn't apply to existing rows and // This constraint is set as NOT VALID so it doesn't apply to existing rows and
@ -193,7 +195,8 @@ impl Action for AlterColumn {
constraint_name = self.not_null_constraint_name(ctx), constraint_name = self.not_null_constraint_name(ctx),
column = self.temporary_column_name(ctx), column = self.temporary_column_name(ctx),
); );
db.run(&query)?; db.run(&query)
.context("failed to add NOT NULL constraint")?;
} }
Ok(()) Ok(())
@ -216,7 +219,7 @@ impl Action for AlterColumn {
existing_name = self.column, existing_name = self.column,
new_name = new_name, new_name = new_name,
); );
db.run(&query)?; db.run(&query).context("failed to rename column")?;
} }
return Ok(()); return Ok(());
} }
@ -237,7 +240,7 @@ impl Action for AlterColumn {
", ",
self.table, column.real_name self.table, column.real_name
); );
db.run(&query)?; db.run(&query).context("failed to drop old column")?;
// Rename temporary column // Rename temporary column
let query = format!( let query = format!(
@ -248,7 +251,8 @@ impl Action for AlterColumn {
temp_column = self.temporary_column_name(ctx), temp_column = self.temporary_column_name(ctx),
name = column_name, name = column_name,
); );
db.run(&query)?; db.run(&query)
.context("failed to rename temporary column")?;
// Remove triggers and procedures // Remove triggers and procedures
let query = format!( let query = format!(
@ -263,7 +267,8 @@ impl Action for AlterColumn {
up_trigger = self.up_trigger_name(ctx), up_trigger = self.up_trigger_name(ctx),
down_trigger = self.down_trigger_name(ctx), down_trigger = self.down_trigger_name(ctx),
); );
db.run(&query)?; db.run(&query)
.context("failed to drop up and down triggers")?;
// Update column to be NOT NULL if necessary // Update column to be NOT NULL if necessary
if !column.nullable { if !column.nullable {
@ -277,7 +282,8 @@ impl Action for AlterColumn {
table = self.table, table = self.table,
constraint_name = self.not_null_constraint_name(ctx), constraint_name = self.not_null_constraint_name(ctx),
); );
db.run(&query)?; db.run(&query)
.context("failed to validate NOT NULL constraint")?;
// Update the column to be NOT NULL. // Update the column to be NOT NULL.
// This requires an exclusive lock but since PG 12 it can check // This requires an exclusive lock but since PG 12 it can check
@ -291,7 +297,7 @@ impl Action for AlterColumn {
table = self.table, table = self.table,
column = column_name, column = column_name,
); );
db.run(&query)?; db.run(&query).context("failed to set column as NOT NULL")?;
// Drop the temporary constraint // Drop the temporary constraint
let query = format!( let query = format!(
@ -302,7 +308,8 @@ impl Action for AlterColumn {
table = self.table, table = self.table,
constraint_name = self.not_null_constraint_name(ctx), constraint_name = self.not_null_constraint_name(ctx),
); );
db.run(&query)?; db.run(&query)
.context("failed to drop NOT NULL constraint")?;
} }
Ok(()) Ok(())
@ -344,7 +351,8 @@ impl Action for AlterColumn {
up_trigger = self.up_trigger_name(ctx), up_trigger = self.up_trigger_name(ctx),
down_trigger = self.down_trigger_name(ctx), down_trigger = self.down_trigger_name(ctx),
); );
db.run(&query)?; db.run(&query)
.context("failed to drop up and down triggers")?;
// Drop temporary column // Drop temporary column
let query = format!( let query = format!(
@ -355,7 +363,7 @@ impl Action for AlterColumn {
table = self.table, table = self.table,
temp_column = self.temporary_column_name(ctx), temp_column = self.temporary_column_name(ctx),
); );
db.run(&query)?; db.run(&query).context("failed to drop temporary column")?;
Ok(()) Ok(())
} }

View File

@ -1,5 +1,6 @@
use super::{Action, Column, MigrationContext}; use super::{Action, Column, MigrationContext};
use crate::{db::Conn, schema::Schema}; use crate::{db::Conn, schema::Schema};
use anyhow::Context;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
@ -71,7 +72,8 @@ impl Action for CreateTable {
)", )",
self.name, self.name,
definition_rows.join(",\n"), definition_rows.join(",\n"),
))?; ))
.context("failed to create table")?;
Ok(()) Ok(())
} }
@ -88,8 +90,8 @@ impl Action for CreateTable {
fn update_schema(&self, _ctx: &MigrationContext, _schema: &mut Schema) {} fn update_schema(&self, _ctx: &MigrationContext, _schema: &mut Schema) {}
fn abort(&self, _ctx: &MigrationContext, db: &mut dyn Conn) -> anyhow::Result<()> { fn abort(&self, _ctx: &MigrationContext, db: &mut dyn Conn) -> anyhow::Result<()> {
let query = format!("DROP TABLE IF EXISTS {table}", table = self.name,); db.run(&format!("DROP TABLE IF EXISTS {}", self.name,))
db.run(&query)?; .context("failed to drop table")?;
Ok(()) Ok(())
} }

View File

@ -1,5 +1,6 @@
use super::{Action, MigrationContext}; use super::{Action, MigrationContext};
use crate::{db::Conn, schema::Schema}; use crate::{db::Conn, schema::Schema};
use anyhow::Context;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
@ -77,7 +78,7 @@ impl Action for RemoveColumn {
table = self.table, table = self.table,
declarations = declarations.join("\n"), declarations = declarations.join("\n"),
); );
db.run(&query)?; db.run(&query).context("failed to create down trigger")?;
} }
Ok(()) Ok(())
@ -102,7 +103,8 @@ impl Action for RemoveColumn {
column = self.column, column = self.column,
trigger_name = self.trigger_name(&ctx), trigger_name = self.trigger_name(&ctx),
); );
db.run(&query)?; db.run(&query)
.context("failed to drop column and down trigger")?;
Ok(()) Ok(())
} }
@ -124,7 +126,8 @@ impl Action for RemoveColumn {
", ",
table = self.table, table = self.table,
trigger_name = self.trigger_name(&ctx), trigger_name = self.trigger_name(&ctx),
))?; ))
.context("failed to drop down trigger")?;
Ok(()) Ok(())
} }

View File

@ -1,5 +1,6 @@
use super::{Action, MigrationContext}; use super::{Action, MigrationContext};
use crate::{db::Conn, schema::Schema}; use crate::{db::Conn, schema::Schema};
use anyhow::Context;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
@ -35,7 +36,7 @@ impl Action for RemoveTable {
", ",
table = self.table, table = self.table,
); );
db.run(&query)?; db.run(&query).context("failed to drop table")?;
Ok(()) Ok(())
} }

View File

@ -1,5 +1,6 @@
use super::{Action, MigrationContext}; use super::{Action, MigrationContext};
use crate::{db::Conn, schema::Schema}; use crate::{db::Conn, schema::Schema};
use anyhow::Context;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
@ -38,7 +39,7 @@ impl Action for RenameTable {
table = self.table, table = self.table,
new_name = self.new_name, new_name = self.new_name,
); );
db.run(&query)?; db.run(&query).context("failed to rename table")?;
Ok(()) Ok(())
} }