Skip to content

Commit 9a55670

Browse files
committed
Support :use_advisory_lock option to Migrator.run to use advisory locks 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.
1 parent c6a1485 commit 9a55670

File tree

4 files changed

+47
-1
lines changed

4 files changed

+47
-1
lines changed

CHANGELOG

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
=== master
22

3+
* Support :use_advisory_lock option to Migrator.run to use advisory locks when running migrations (jeremyevans) (#2089)
4+
35
* Support Database#with_advisory_lock on PostgreSQL, MySQL, and Microsoft SQL Server (jeremyevans) (#2089)
46

57
=== 5.77.0 (2024-02-01)

lib/sequel/extensions/migration.rb

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,11 @@ def self.is_current?(db, directory, opts=OPTS)
403403
migrator_class(directory).new(db, directory, opts).is_current?
404404
end
405405

406+
# Lock ID to use for advisory locks when running migrations
407+
# "sequel-migration".codepoints.reduce(:*) % (2**63)
408+
MIGRATION_ADVISORY_LOCK_ID = 4966325471869609408
409+
private_constant :MIGRATION_ADVISORY_LOCK_ID
410+
406411
# Migrates the supplied database using the migration files in the specified directory. Options:
407412
# :allow_missing_migration_files :: Don't raise an error if there are missing migration files.
408413
# It is very risky to use this option, since it can result in
@@ -416,14 +421,20 @@ def self.is_current?(db, directory, opts=OPTS)
416421
# :table :: The table containing the schema version (default: :schema_info for integer migrations and
417422
# :schema_migrations for timestamped migrations).
418423
# :target :: The target version to which to migrate. If not given, migrates to the maximum version.
424+
# :use_advisory_lock :: Use advisory locks in migrations (only use this if Sequel supports advisory
425+
# locks for the database).
419426
#
420427
# Examples:
421428
# Sequel::Migrator.run(DB, "migrations")
422429
# Sequel::Migrator.run(DB, "migrations", target: 15, current: 10)
423430
# Sequel::Migrator.run(DB, "app1/migrations", column: :app2_version)
424431
# Sequel::Migrator.run(DB, "app2/migrations", column: :app2_version, table: :schema_info2)
425432
def self.run(db, directory, opts=OPTS)
426-
migrator_class(directory).new(db, directory, opts).run
433+
if opts[:use_advisory_lock]
434+
db.with_advisory_lock(MIGRATION_ADVISORY_LOCK_ID){run(db, directory, opts.merge(:use_advisory_lock=>false))}
435+
else
436+
migrator_class(directory).new(db, directory, opts).run
437+
end
427438
end
428439

429440
# Choose the Migrator subclass to use. Uses the TimestampMigrator

spec/extensions/migration_spec.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,27 @@ def alter_table(*args, &block)
336336
end
337337
end
338338

339+
describe "Sequel::Migrator.run" do
340+
before do
341+
@db = Sequel.mock
342+
end
343+
344+
it "should use with_advisory_lock on database if :use_advisory_lock option is given" do
345+
@db.define_singleton_method(:with_advisory_lock){|lock_id, &block| execute("get-lock(#{lock_id})"); block.call; execute("release-lock(#{lock_id})")}
346+
Sequel::Migrator.run(@db, "spec/files/integer_migrations", :use_advisory_lock=>true)
347+
@db.sqls.values_at(0, 1, -2, -1).must_equal [
348+
"get-lock(4966325471869609408)",
349+
"SELECT NULL AS nil FROM schema_info LIMIT 1",
350+
"UPDATE schema_info SET version = 3",
351+
"release-lock(4966325471869609408)"
352+
]
353+
end
354+
355+
it "should raise NoMethodError if with_advisory_lock is not supported on database and :use_advisory_lock option is given" do
356+
proc{Sequel::Migrator.run(@db, "spec/files/integer_migrations", :use_advisory_lock=>true)}.must_raise NoMethodError
357+
end
358+
end
359+
339360
describe "Sequel::IntegerMigrator" do
340361
before do
341362
dbc = Class.new(Sequel::Mock::Database) do

spec/integration/migrator_test.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,18 @@
1919
[:sm1111, :sm2222, :sm3333].each{|n| @db.table_exists?(n).must_equal false}
2020
@db[:schema_info].get(:version).must_equal 0
2121
end
22+
23+
if DB.respond_to?(:with_advisory_lock)
24+
it "should be able to migrate up and down all the way successfully with :use_advisory_lock option" do
25+
@dir = 'spec/files/integer_migrations'
26+
@m.run(@db, @dir, :use_advisory_lock=>true)
27+
[:schema_info, :sm1111, :sm2222, :sm3333].each{|n| @db.table_exists?(n).must_equal true}
28+
@db[:schema_info].get(:version).must_equal 3
29+
@m.run(@db, @dir, :target=>0, :use_advisory_lock=>true)
30+
[:sm1111, :sm2222, :sm3333].each{|n| @db.table_exists?(n).must_equal false}
31+
@db[:schema_info].get(:version).must_equal 0
32+
end
33+
end
2234

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

0 commit comments

Comments
 (0)