Skip to content

Commit

Permalink
Support :use_advisory_lock option to Migrator.run to use advisory loc…
Browse files Browse the repository at this point in the history
…ks when running migrations

I'm not yet comfortable turning this on by default yet (if the
database supports it).  However, that's something that could be
considered in future versions.
  • Loading branch information
jeremyevans committed Feb 2, 2024
1 parent c6a1485 commit 9a55670
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 1 deletion.
2 changes: 2 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
=== master

* Support :use_advisory_lock option to Migrator.run to use advisory locks when running migrations (jeremyevans) (#2089)

* Support Database#with_advisory_lock on PostgreSQL, MySQL, and Microsoft SQL Server (jeremyevans) (#2089)

=== 5.77.0 (2024-02-01)
Expand Down
13 changes: 12 additions & 1 deletion lib/sequel/extensions/migration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,11 @@ def self.is_current?(db, directory, opts=OPTS)
migrator_class(directory).new(db, directory, opts).is_current?
end

# Lock ID to use for advisory locks when running migrations
# "sequel-migration".codepoints.reduce(:*) % (2**63)
MIGRATION_ADVISORY_LOCK_ID = 4966325471869609408
private_constant :MIGRATION_ADVISORY_LOCK_ID

# Migrates the supplied database using the migration files in the specified directory. Options:
# :allow_missing_migration_files :: Don't raise an error if there are missing migration files.
# It is very risky to use this option, since it can result in
Expand All @@ -416,14 +421,20 @@ def self.is_current?(db, directory, opts=OPTS)
# :table :: The table containing the schema version (default: :schema_info for integer migrations and
# :schema_migrations for timestamped migrations).
# :target :: The target version to which to migrate. If not given, migrates to the maximum version.
# :use_advisory_lock :: Use advisory locks in migrations (only use this if Sequel supports advisory
# locks for the database).
#
# Examples:
# Sequel::Migrator.run(DB, "migrations")
# Sequel::Migrator.run(DB, "migrations", target: 15, current: 10)
# Sequel::Migrator.run(DB, "app1/migrations", column: :app2_version)
# Sequel::Migrator.run(DB, "app2/migrations", column: :app2_version, table: :schema_info2)
def self.run(db, directory, opts=OPTS)
migrator_class(directory).new(db, directory, opts).run
if opts[:use_advisory_lock]
db.with_advisory_lock(MIGRATION_ADVISORY_LOCK_ID){run(db, directory, opts.merge(:use_advisory_lock=>false))}
else
migrator_class(directory).new(db, directory, opts).run
end
end

# Choose the Migrator subclass to use. Uses the TimestampMigrator
Expand Down
21 changes: 21 additions & 0 deletions spec/extensions/migration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,27 @@ def alter_table(*args, &block)
end
end

describe "Sequel::Migrator.run" do
before do
@db = Sequel.mock
end

it "should use with_advisory_lock on database if :use_advisory_lock option is given" do
@db.define_singleton_method(:with_advisory_lock){|lock_id, &block| execute("get-lock(#{lock_id})"); block.call; execute("release-lock(#{lock_id})")}
Sequel::Migrator.run(@db, "spec/files/integer_migrations", :use_advisory_lock=>true)
@db.sqls.values_at(0, 1, -2, -1).must_equal [
"get-lock(4966325471869609408)",
"SELECT NULL AS nil FROM schema_info LIMIT 1",
"UPDATE schema_info SET version = 3",
"release-lock(4966325471869609408)"
]
end

it "should raise NoMethodError if with_advisory_lock is not supported on database and :use_advisory_lock option is given" do
proc{Sequel::Migrator.run(@db, "spec/files/integer_migrations", :use_advisory_lock=>true)}.must_raise NoMethodError
end
end

describe "Sequel::IntegerMigrator" do
before do
dbc = Class.new(Sequel::Mock::Database) do
Expand Down
12 changes: 12 additions & 0 deletions spec/integration/migrator_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,18 @@
[:sm1111, :sm2222, :sm3333].each{|n| @db.table_exists?(n).must_equal false}
@db[:schema_info].get(:version).must_equal 0
end

if DB.respond_to?(:with_advisory_lock)
it "should be able to migrate up and down all the way successfully with :use_advisory_lock option" do
@dir = 'spec/files/integer_migrations'
@m.run(@db, @dir, :use_advisory_lock=>true)
[:schema_info, :sm1111, :sm2222, :sm3333].each{|n| @db.table_exists?(n).must_equal true}
@db[:schema_info].get(:version).must_equal 3
@m.run(@db, @dir, :target=>0, :use_advisory_lock=>true)
[:sm1111, :sm2222, :sm3333].each{|n| @db.table_exists?(n).must_equal false}
@db[:schema_info].get(:version).must_equal 0
end
end

it "should be able to migrate up and down to specific versions successfully" do
@dir = 'spec/files/integer_migrations'
Expand Down

0 comments on commit 9a55670

Please sign in to comment.