Skip to content

Commit 6046279

Browse files
authored
Allow running certain migrations outside of a transaction. Fixes #1129 (#1140)
1 parent 9247b79 commit 6046279

2 files changed

Lines changed: 52 additions & 5 deletions

File tree

spec/avram/migrator/migration_spec.cr

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,21 @@ class MigrationCreateAndDropIndexes::V993 < Avram::Migrator::Migration::V1
9898
end
9999
end
100100

101+
class MigrationWithUnsafeSQL::V992 < Avram::Migrator::Migration::V1
102+
unsafe_migration
103+
104+
def migrate
105+
execute "CREATE TABLE execution_order (x integer NOT NULL);"
106+
# Postgres doesn't like running concurrent index creation inside of a transaction
107+
execute "CREATE INDEX CONCURRENTLY fast_index ON execution_order (x)"
108+
end
109+
110+
def rollback
111+
execute "DROP INDEX CONCURRENTLY IF EXISTS fast_index"
112+
drop :execution_order
113+
end
114+
end
115+
101116
describe Avram::Migrator::Migration::V1 do
102117
it "executes statements in a transaction" do
103118
expect_raises Avram::FailedMigration do
@@ -185,6 +200,20 @@ describe Avram::Migrator::Migration::V1 do
185200
sql.should contain "DROP SEQUENCE IF EXISTS accounts_number_seq"
186201
end
187202
end
203+
204+
# NOTE: This spec is run with truncation to ensure we're not wrapping it in a transaction
205+
describe "outside of a transaction", tags: Avram::SpecHelper::TRUNCATE do
206+
it "allows running unsafe SQL outside of a transaction" do
207+
migration = MigrationWithUnsafeSQL::V992.new
208+
migration.up(quiet: true)
209+
sql = migration.prepared_statements.join("\n")
210+
sql.should contain "CREATE INDEX CONCURRENTLY fast_index ON execution_order (x)"
211+
212+
migration.down(quiet: true)
213+
sql = migration.prepared_statements.join("\n")
214+
sql.should contain "DROP INDEX CONCURRENTLY IF EXISTS fast_index"
215+
end
216+
end
188217
end
189218

190219
private def get_column_names(table_name)

src/avram/migrator/migration.cr

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,19 @@ abstract class Avram::Migrator::Migration::V1
1818
end
1919
end
2020

21+
# This macro will tell the migration to run all prepared statements
22+
# outside of a transaction. This is considered "unsafe" as improper
23+
# SQL could pose risk to data integrity if not handled correctly
24+
macro unsafe_migration
25+
def run_in_transaction : Bool
26+
false
27+
end
28+
end
29+
30+
def run_in_transaction : Bool
31+
true
32+
end
33+
2134
abstract def migrate
2235
abstract def version : Int64
2336

@@ -34,7 +47,7 @@ abstract class Avram::Migrator::Migration::V1
3447
else
3548
reset_prepared_statements
3649
migrate
37-
execute_in_transaction @prepared_statements do |txn|
50+
run_prepared_statements @prepared_statements do |txn|
3851
track_migration(txn)
3952
unless quiet
4053
puts "Migrated #{self.class.name.colorize(:green)}"
@@ -52,7 +65,7 @@ abstract class Avram::Migrator::Migration::V1
5265
else
5366
reset_prepared_statements
5467
rollback
55-
execute_in_transaction @prepared_statements do |txn|
68+
run_prepared_statements @prepared_statements do |txn|
5669
untrack_migration(txn)
5770
unless quiet
5871
puts "Rolled back #{self.class.name.colorize(:green)}"
@@ -93,13 +106,18 @@ abstract class Avram::Migrator::Migration::V1
93106
# # Usage
94107
#
95108
# ```
96-
# execute_in_transaction ["DROP TABLE comments;"] do |db|
109+
# run_prepared_statements ["DROP TABLE comments;"] do |db|
97110
# db.exec "DROP TABLE users;"
98111
# end
99112
# ```
100-
private def execute_in_transaction(statements : Array(String), &)
113+
private def run_prepared_statements(statements : Array(String), &)
101114
database = Avram.settings.database_to_migrate
102-
database.transaction do
115+
if run_in_transaction
116+
database.transaction do
117+
statements.each { |sql| database.exec sql }
118+
yield database
119+
end
120+
else
103121
statements.each { |sql| database.exec sql }
104122
yield database
105123
end

0 commit comments

Comments
 (0)