FIX: Make non-transactional migration idempotent (#13608)

FIX: Make non-transactional migration idempotent (#13608)

Since disable_ddl_transaction! is disabled for this migration, it needs to be idempotent. Any error during the migration (e.g. a timeout) will cause ActiveRecord to fail the migration, and try again on the next run. If the index had already been created during the first run, then an ‘already exists’ error will be raised, with no way to recover.

Unfortunately an ActiveRecord bug prevents us from using if_not_exists: true alongside algorithm: :concurrently, so we have to drop to raw SQL.

diff --git a/db/migrate/20210624080131_add_partial_index_pinned_until.rb b/db/migrate/20210624080131_add_partial_index_pinned_until.rb
index c91cdb4..14bd0d4 100644
--- a/db/migrate/20210624080131_add_partial_index_pinned_until.rb
+++ b/db/migrate/20210624080131_add_partial_index_pinned_until.rb
@@ -3,9 +3,21 @@
 class AddPartialIndexPinnedUntil < ActiveRecord::Migration[6.1]
   disable_ddl_transaction!
 
-  def change
-    add_index :topics, :pinned_until,
-      where: 'pinned_until IS NOT NULL',
-      algorithm: :concurrently
+  # Dropping to raw SQL here due to an ActiveRecord bug which prevents
+  # using `algorithm: :concurrently` and `if_not_exists: true`
+  # https://github.com/rails/rails/pull/41490
+
+  def up
+    execute <<~SQL
+      CREATE INDEX CONCURRENTLY IF NOT EXISTS "index_topics_on_pinned_until"
+      ON "topics" ("pinned_until")
+      WHERE pinned_until IS NOT NULL
+    SQL
+  end
+
+  def down
+    execute <<~SQL
+      DROP INDEX CONCURRENTLY IF EXISTS "index_topics_on_pinned_until"
+    SQL
   end
 end

GitHub sha: 9428a669b52ab313f0ead3b20ad12f114b17d621

This commit appears in #13608 which was approved by markvanlan. It was merged by davidtaylorhq.