@@ -3,14 +3,25 @@ use crate::{
3 db::{Conn, Transaction},
3 db::{Conn, Transaction},
4 schema::Schema,
4 schema::Schema,
6−use anyhow::Context;
6+use anyhow::{bail, Context};
7use serde::{Deserialize, Serialize};
7use serde::{Deserialize, Serialize};
9#[derive(Serialize, Deserialize, Debug)]
9#[derive(Serialize, Deserialize, Debug)]
10pub struct RemoveColumn {
10pub struct RemoveColumn {
11 pub table: String,
11 pub table: String,
12 pub column: String,
12 pub column: String,
13− pub down: Option<String>,
13+ pub down: Option<Transformation>,
16+#[derive(Serialize, Deserialize, Debug)]
18+pub enum Transformation {
16impl RemoveColumn {
27impl RemoveColumn {
@@ -39,10 +50,10 @@ impl Action for RemoveColumn {
39 db: &mut dyn Conn,
50 db: &mut dyn Conn,
40 schema: &Schema,
51 schema: &Schema,
41 ) -> anyhow::Result<()> {
52 ) -> anyhow::Result<()> {
53+ let table = schema.get_table(db, &self.table)?;
42 // Add down trigger
55 // Add down trigger
43 if let Some(down) = &self.down {
56 if let Some(down) = &self.down {
44− let table = schema.get_table(db, &self.table)?;
46 let declarations: Vec<String> = table
57 let declarations: Vec<String> = table
@@ -56,32 +67,91 @@ impl Action for RemoveColumn {
57 .collect();
68 .collect();
70+ if let Transformation::Simple(down) = down {
59− let query = format!(
71+ let query = format!(
61− CREATE OR REPLACE FUNCTION {trigger_name}()
73+ CREATE OR REPLACE FUNCTION {trigger_name}()
62− RETURNS TRIGGER AS $$
74+ RETURNS TRIGGER AS $$
64− IF reshape.is_new_schema() THEN
76+ IF reshape.is_new_schema() THEN
66− {declarations}
78+ {declarations}
68− NEW.{column_name} = {down};
80+ NEW.{column_name} = {down};
71− RETURN NEW;
83+ RETURN NEW;
73− $$ language 'plpgsql';
85+ $$ language 'plpgsql';
75− DROP TRIGGER IF EXISTS "{trigger_name}" ON "{table}";
87+ DROP TRIGGER IF EXISTS "{trigger_name}" ON "{table}";
76− CREATE TRIGGER "{trigger_name}" BEFORE UPDATE OR INSERT ON "{table}" FOR EACH ROW EXECUTE PROCEDURE {trigger_name}();
88+ CREATE TRIGGER "{trigger_name}" BEFORE UPDATE OR INSERT ON "{table}" FOR EACH ROW EXECUTE PROCEDURE {trigger_name}();
78− column_name = self.column,
90+ column_name = self.column,
79− trigger_name = self.trigger_name(ctx),
91+ trigger_name = self.trigger_name(ctx),
80− down = down,
92+ down = down,
81− table = self.table,
93+ table = self.table,
82− declarations = declarations.join("\n"),
94+ declarations = declarations.join("\n"),
84− db.run(&query).context("failed to create down trigger")?;
96+ db.run(&query).context("failed to create down trigger")?;
99+ if let Transformation::Update {
105+ let existing_schema_name = match &ctx.existing_schema_name {
107+ None => bail!("can't use update without previous migration"),
110+ let from_table = schema.get_table(db, &from_table)?;
112+ let declarations: Vec<String> = from_table
117+ "{alias} public.{table}.{real_name}%TYPE := NEW.{real_name};",
118+ table = from_table.real_name,
119+ alias = column.name,
120+ real_name = column.real_name,
125+ let query = format!(
127+ CREATE OR REPLACE FUNCTION {trigger_name}()
128+ RETURNS TRIGGER AS $$
129+ #variable_conflict use_variable
131+ IF reshape.is_new_schema() THEN
135+ UPDATE "migration_{existing_schema_name}"."{changed_table}" "{changed_table}"
136+ SET "{column_name}" = {value}
142+ $$ language 'plpgsql';
144+ DROP TRIGGER IF EXISTS "{trigger_name}" ON "{from_table_real}";
145+ CREATE TRIGGER "{trigger_name}" BEFORE UPDATE OR INSERT ON "{from_table_real}" FOR EACH ROW EXECUTE PROCEDURE {trigger_name}();
147+ changed_table = self.table,
148+ from_table_real = from_table.real_name,
149+ column_name = self.column,
150+ trigger_name = self.trigger_name(ctx),
151+ declarations = declarations.join("\n"),
153+ db.run(&query).context("failed to create down trigger")?;
@@ -106,12 +176,20 @@ impl Action for RemoveColumn {
108 // Remove column, function and trigger
178 // Remove column, function and trigger
179+ let trigger_table = match &self.down {
180+ Some(Transformation::Update {
109 let query = format!(
187 let query = format!(
111 ALTER TABLE "{table}"
189 ALTER TABLE "{table}"
112 DROP COLUMN IF EXISTS "{column}";
190 DROP COLUMN IF EXISTS "{column}";
114− DROP TRIGGER IF EXISTS "{trigger_name}" ON "{table}";
192+ DROP TRIGGER IF EXISTS "{trigger_name}" ON "{trigger_table}";
115 DROP FUNCTION IF EXISTS "{trigger_name}";
193 DROP FUNCTION IF EXISTS "{trigger_name}";
117 table = self.table,
195 table = self.table,
@@ -134,12 +212,19 @@ impl Action for RemoveColumn {
135 fn abort(&self, ctx: &MigrationContext, db: &mut dyn Conn) -> anyhow::Result<()> {
213 fn abort(&self, ctx: &MigrationContext, db: &mut dyn Conn) -> anyhow::Result<()> {
136 // Remove function and trigger
214 // Remove function and trigger
215+ let trigger_table = match &self.down {
216+ Some(Transformation::Update {
137 db.run(&format!(
223 db.run(&format!(
139− DROP TRIGGER IF EXISTS "{trigger_name}" ON "{table}";
225+ DROP TRIGGER IF EXISTS "{trigger_name}" ON "{trigger_table}";
140 DROP FUNCTION IF EXISTS "{trigger_name}";
226 DROP FUNCTION IF EXISTS "{trigger_name}";
143 trigger_name = self.trigger_name(ctx),
228 trigger_name = self.trigger_name(ctx),
145 .context("failed to drop down trigger")?;
230 .context("failed to drop down trigger")?;