@@ -150,6 +150,33 @@ impl Action for AlterColumn {
150 common::batch_touch_rows(db, &table.real_name, &column.real_name)
150 common::batch_touch_rows(db, &table.real_name, &column.real_name)
151 .context("failed to batch update existing rows")?;
151 .context("failed to batch update existing rows")?;
153+ // Duplicate any indices to the temporary column
154+ let indices = common::get_indices_for_column(db, &table.real_name, &column.real_name)?;
155+ for (index_name, index_oid) in indices {
156+ let index_columns: Vec<String> = common::get_index_columns(db, &index_name)?
159+ // Replace column with temporary column for new index
160+ if idx_column == column.real_name {
161+ temporary_column_name.to_string()
167+ let temp_index_name = self.temp_index_name(ctx, index_oid);
171+ CREATE INDEX CONCURRENTLY IF NOT EXISTS "{new_index_name}" ON "{table}" ({columns})
173+ new_index_name = temp_index_name,
174+ table = table.real_name,
175+ columns = index_columns.join(", "),
177+ .context("failed to create temporary index")?;
153 // Add a temporary NOT NULL constraint if the column shouldn't be nullable.
180 // Add a temporary NOT NULL constraint if the column shouldn't be nullable.
154 // This constraint is set as NOT VALID so it doesn't apply to existing rows and
181 // This constraint is set as NOT VALID so it doesn't apply to existing rows and
155 // the existing rows don't need to be scanned under an exclusive lock.
182 // the existing rows don't need to be scanned under an exclusive lock.
@@ -246,10 +273,52 @@ impl Action for AlterColumn {
246 .context("failed to drop NOT NULL constraint")?;
273 .context("failed to drop NOT NULL constraint")?;
276+ // Replace old indices with the new temporary ones created for the temporary column
277+ let indices = common::get_indices_for_column(db, &self.table, &self.column)?;
278+ for (current_index_name, index_oid) in indices {
279+ // To keep the index handling idempotent, we need to do the following:
280+ // 1. Add a prefix to the existing index
281+ // 2. Rename temporary index to its final name
282+ // 3. Drop existing index concurrently
284+ // Add prefix (if not already added) to existing index
285+ let prefix = "__reshape_old";
286+ let target_index_name = current_index_name.trim_start_matches(prefix);
287+ let old_index_name = format!("{}_{}", prefix, target_index_name);
290+ ALTER INDEX IF EXISTS "{current_name}" RENAME TO "{new_name}"
292+ current_name = target_index_name,
293+ new_name = old_index_name,
295+ .context("failed to rename old index")?;
297+ // Rename temporary index to real name
298+ let temp_index_name = self.temp_index_name(ctx, index_oid);
301+ ALTER INDEX IF EXISTS "{temp_index_name}" RENAME TO "{target_index_name}"
303+ temp_index_name = temp_index_name,
304+ target_index_name = target_index_name,
306+ .context("failed to rename temporary index")?;
308+ // Drop old index concurrently
311+ DROP INDEX CONCURRENTLY IF EXISTS "{old_index_name}"
313+ old_index_name = old_index_name,
315+ .context("failed to drop old index")?;
249 // Remove old column
318 // Remove old column
250 let query = format!(
319 let query = format!(
252− ALTER TABLE "{table}" DROP COLUMN "{column}" CASCADE
321+ ALTER TABLE "{table}" DROP COLUMN IF EXISTS "{column}" CASCADE
254 table = self.table,
323 table = self.table,
255 column = self.column,
324 column = self.column,
@@ -311,6 +380,19 @@ impl Action for AlterColumn {
313 fn abort(&self, ctx: &MigrationContext, db: &mut dyn Conn) -> anyhow::Result<()> {
382 fn abort(&self, ctx: &MigrationContext, db: &mut dyn Conn) -> anyhow::Result<()> {
383+ // Safely remove any indices created for the temporary column
384+ let temp_column_name = self.temporary_column_name(ctx);
385+ let indices = common::get_indices_for_column(db, &self.table, &temp_column_name)?;
386+ for (_, index_oid) in indices {
387+ let temp_index_name = self.temp_index_name(ctx, index_oid);
390+ DROP INDEX CONCURRENTLY IF EXISTS "{index_name}"
392+ index_name = temp_index_name,
314 // Drop temporary column
396 // Drop temporary column
315 let query = format!(
397 let query = format!(
@@ -359,6 +441,10 @@ impl AlterColumn {
359 format!("{}_alter_column_temporary", ctx.prefix())
441 format!("{}_alter_column_temporary", ctx.prefix())
444+ fn temp_index_name(&self, ctx: &MigrationContext, index_oid: u32) -> String {
445+ format!("{}_alter_column_temp_index_{}", ctx.prefix(), index_oid)
362 fn can_short_circuit(&self) -> bool {
448 fn can_short_circuit(&self) -> bool {
363 self.changes.name.is_some()
449 self.changes.name.is_some()
364 && self.changes.data_type.is_none()
450 && self.changes.data_type.is_none()