mimi-db 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/mimi/db/helpers.rb +53 -0
- data/lib/mimi/db/lock/mysql_lock.rb +56 -0
- data/lib/mimi/db/lock/postgresql_lock.rb +84 -0
- data/lib/mimi/db/lock/sqlite_lock.rb +72 -0
- data/lib/mimi/db/lock.rb +73 -0
- data/lib/mimi/db/version.rb +1 -1
- data/lib/mimi/db.rb +1 -24
- data/mimi-db.gemspec +0 -1
- metadata +7 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9aec6438056a412a0d1807eb550ecae64eea936e
|
4
|
+
data.tar.gz: 7abc14cb1ee6040f9af0f5dc0852fd464c5a1f2e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a67de7fe7f9d7cf7c6785b8de1bf8cbfdeaf96bcf99a5957f56828f4111c683f090e88ef3ba658319685e650d573b2d7da07664c5efb6f9c98beb322631391e3
|
7
|
+
data.tar.gz: d339acb6afd0affa0b6905eab0502f97801adc3e6c460300d3502725395e53bcd843a7808e74d1e0070a63c7e850eda01ccb704ee9e7235c1f9d09670ec136b7
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Mimi
|
2
|
+
module DB
|
3
|
+
module Helpers
|
4
|
+
#
|
5
|
+
# Returns a list of model classes
|
6
|
+
#
|
7
|
+
# @return [Array<ActiveRecord::Base>]
|
8
|
+
#
|
9
|
+
def models
|
10
|
+
ActiveRecord::Base.descendants
|
11
|
+
end
|
12
|
+
|
13
|
+
# Migrates the schema for known models
|
14
|
+
#
|
15
|
+
def migrate_schema!
|
16
|
+
models.each(&:auto_upgrade!)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Creates the database specified in the current configuration.
|
20
|
+
#
|
21
|
+
def create!
|
22
|
+
ActiveRecord::Tasks::DatabaseTasks.root = Mimi.app_root_path
|
23
|
+
ActiveRecord::Tasks::DatabaseTasks.create(Mimi::DB.active_record_config)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Drops the database specified in the current configuration.
|
27
|
+
#
|
28
|
+
def drop!
|
29
|
+
ActiveRecord::Tasks::DatabaseTasks.root = Mimi.app_root_path
|
30
|
+
ActiveRecord::Tasks::DatabaseTasks.drop(Mimi::DB.active_record_config)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Clears (but not drops) the database specified in the current configuration.
|
34
|
+
#
|
35
|
+
def clear!
|
36
|
+
ActiveRecord::Tasks::DatabaseTasks.root = Mimi.app_root_path
|
37
|
+
ActiveRecord::Tasks::DatabaseTasks.purge(Mimi::DB.active_record_config)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Executes raw SQL, with variables interpolation.
|
41
|
+
#
|
42
|
+
# @example
|
43
|
+
# Mimi::DB.execute('insert into table1 values(?, ?, ?)', 'foo', :bar, 123)
|
44
|
+
#
|
45
|
+
def execute(statement, *args)
|
46
|
+
sql = ActiveRecord::Base.send(:replace_bind_variables, statement, args)
|
47
|
+
ActiveRecord::Base.connection.execute(sql)
|
48
|
+
end
|
49
|
+
end # module Helpers
|
50
|
+
|
51
|
+
extend Mimi::DB::Helpers
|
52
|
+
end # module DB
|
53
|
+
end # module Mimi
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Mimi
|
2
|
+
module DB
|
3
|
+
module Lock
|
4
|
+
class MysqlLock
|
5
|
+
attr_reader :name, :name_uint64, :options, :timeout
|
6
|
+
|
7
|
+
#
|
8
|
+
# Timeout semantics:
|
9
|
+
# nil -- wait indefinitely
|
10
|
+
# 0 -- do not wait
|
11
|
+
# <s> -- wait <s> seconds (can be Float)
|
12
|
+
#
|
13
|
+
def initialize(name, opts = {})
|
14
|
+
@name = name
|
15
|
+
@name_uint64 = Digest::SHA1.digest(name).unpack('q').first
|
16
|
+
@options = opts
|
17
|
+
@timeout =
|
18
|
+
if opts[:timeout].nil?
|
19
|
+
-1
|
20
|
+
elsif opts[:timeout] <= 0
|
21
|
+
0
|
22
|
+
else
|
23
|
+
opts[:timeout].to_f.round
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def execute(&_block)
|
28
|
+
ActiveRecord::Base.transaction(requires_new: true) do
|
29
|
+
begin
|
30
|
+
acquire_lock_with_timeout!
|
31
|
+
yield if block_given?
|
32
|
+
ensure
|
33
|
+
release_lock!
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
#
|
41
|
+
def acquire_lock_with_timeout!
|
42
|
+
result = Mimi::DB.execute('select get_lock(?, ?) as lock_acquired', name, timeout)
|
43
|
+
lock_acquired = result.first[0] == 1
|
44
|
+
raise Mimi::DB::Lock::NotAvailable unless lock_acquired
|
45
|
+
true
|
46
|
+
end
|
47
|
+
|
48
|
+
#
|
49
|
+
def release_lock!
|
50
|
+
Mimi::DB.execute('select release_lock(?)', name)
|
51
|
+
true
|
52
|
+
end
|
53
|
+
end # class MysqlLock
|
54
|
+
end # module Lock
|
55
|
+
end # module DB
|
56
|
+
end # module Mimi
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Mimi
|
2
|
+
module DB
|
3
|
+
module Lock
|
4
|
+
class PostgresqlLock
|
5
|
+
attr_reader :name, :name_uint64, :options, :timeout
|
6
|
+
|
7
|
+
#
|
8
|
+
# Timeout semantics:
|
9
|
+
# nil -- wait indefinitely
|
10
|
+
# 0 -- do not wait
|
11
|
+
# <s> -- wait <s> seconds (can be Float)
|
12
|
+
#
|
13
|
+
def initialize(name, opts = {})
|
14
|
+
@name = name
|
15
|
+
@name_uint64 = Digest::SHA1.digest(name).unpack('q').first
|
16
|
+
@options = opts
|
17
|
+
@timeout =
|
18
|
+
if opts[:timeout].nil?
|
19
|
+
0
|
20
|
+
elsif opts[:timeout] <= 0
|
21
|
+
:nowait
|
22
|
+
else
|
23
|
+
opts[:timeout]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def execute(&_block)
|
28
|
+
ActiveRecord::Base.transaction(requires_new: true) do
|
29
|
+
acquire_lock_with_timeout!
|
30
|
+
yield if block_given?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
# Returns current database connection setting for the lock timeout.
|
37
|
+
# Value of 0 means the lock timeout is not set.
|
38
|
+
#
|
39
|
+
# @return [Float] Lock timeout in seconds
|
40
|
+
#
|
41
|
+
def lock_timeout
|
42
|
+
result = Mimi::DB.execute('select setting from pg_settings where name = ?', :lock_timeout)
|
43
|
+
value = result.first['setting'].to_i
|
44
|
+
value = value.to_f / 1000 unless value == 0
|
45
|
+
value
|
46
|
+
end
|
47
|
+
|
48
|
+
# Sets the current database connection setting for the lock timeout.
|
49
|
+
# Value of 0 means the lock operations should never timeout.
|
50
|
+
#
|
51
|
+
# @param [Float,Fixnum] value Lock timeout in seconds
|
52
|
+
#
|
53
|
+
def lock_timeout=(value)
|
54
|
+
raise ArgumentError, 'Numeric value expected as timeout' unless value.is_a?(Numeric)
|
55
|
+
value = (value * 1000).to_i
|
56
|
+
Mimi::DB.execute('update pg_settings set setting = ? where name = ?', value, :lock_timeout)
|
57
|
+
end
|
58
|
+
|
59
|
+
#
|
60
|
+
def acquire_lock_with_timeout!
|
61
|
+
unless timeout == :nowait
|
62
|
+
old_timeout = lock_timeout
|
63
|
+
self.lock_timeout = timeout unless timeout == old_timeout
|
64
|
+
end
|
65
|
+
if timeout == :nowait
|
66
|
+
result = Mimi::DB.execute('select pg_try_advisory_xact_lock(?) as lock_acquired', name_uint64)
|
67
|
+
lock_acquired = result.first['lock_acquired'] == 't'
|
68
|
+
raise Mimi::DB::Lock::NotAvailable unless lock_acquired
|
69
|
+
else
|
70
|
+
begin
|
71
|
+
Mimi::DB.execute('select pg_advisory_xact_lock(?)', name_uint64)
|
72
|
+
rescue ActiveRecord::StatementInvalid
|
73
|
+
raise Mimi::DB::Lock::NotAvailable
|
74
|
+
end
|
75
|
+
# NOTE: in case of a timeout the lock_timeout value will not be set back manually
|
76
|
+
# .. it is expected to roll back with the transaction
|
77
|
+
self.lock_timeout = old_timeout unless timeout == old_timeout
|
78
|
+
end
|
79
|
+
true
|
80
|
+
end
|
81
|
+
end # class PostgresqlLock
|
82
|
+
end # module Lock
|
83
|
+
end # module DB
|
84
|
+
end # module Mimi
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'filelock'
|
2
|
+
|
3
|
+
module Mimi
|
4
|
+
module DB
|
5
|
+
module Lock
|
6
|
+
class SqliteLock
|
7
|
+
attr_reader :name, :name_digest, :lock_filename, :options, :timeout
|
8
|
+
|
9
|
+
#
|
10
|
+
# Timeout semantics:
|
11
|
+
# nil -- wait indefinitely
|
12
|
+
# 0 -- do not wait
|
13
|
+
# <s> -- wait <s> seconds (can be Float)
|
14
|
+
#
|
15
|
+
def initialize(name, opts = {})
|
16
|
+
@name = name
|
17
|
+
@name_digest = Digest::SHA1.hexdigest(name).first(16)
|
18
|
+
@options = opts
|
19
|
+
@timeout =
|
20
|
+
if opts[:timeout].nil?
|
21
|
+
-1
|
22
|
+
elsif opts[:timeout] <= 0
|
23
|
+
0.100
|
24
|
+
else
|
25
|
+
opts[:timeout].to_f.round
|
26
|
+
end
|
27
|
+
db_filename = Pathname.new(Mimi::DB.module_options[:db_database]).expand_path
|
28
|
+
@lock_filename = "#{db_filename}.lock-#{name_digest}"
|
29
|
+
@lock_acquired = nil
|
30
|
+
@file = nil
|
31
|
+
end
|
32
|
+
|
33
|
+
def execute(&_block)
|
34
|
+
ActiveRecord::Base.transaction(requires_new: true) do
|
35
|
+
begin
|
36
|
+
acquire_lock_with_timeout!
|
37
|
+
yield if block_given?
|
38
|
+
ensure
|
39
|
+
release_lock!
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
#
|
47
|
+
def acquire_lock_with_timeout!
|
48
|
+
Mimi::DB.logger.info "! Locking file: #{lock_filename}"
|
49
|
+
|
50
|
+
@file = File.open(lock_filename, File::RDWR | File::CREAT, 0644)
|
51
|
+
if timeout
|
52
|
+
Timeout.timeout(timeout, Mimi::DB::Lock::NotAvailable) { @file.flock(File::LOCK_EX) }
|
53
|
+
else
|
54
|
+
@file.flock(File::LOCK_EX)
|
55
|
+
end
|
56
|
+
@lock_acquired = true
|
57
|
+
true
|
58
|
+
end
|
59
|
+
|
60
|
+
#
|
61
|
+
def release_lock!
|
62
|
+
Mimi::DB.logger.info "! Unlocking file: #{lock_filename}"
|
63
|
+
@file.flock(File::LOCK_UN) if @lock_acquired
|
64
|
+
@file.close
|
65
|
+
# NOTE: do not unlink file here, it leads to a potential race condition:
|
66
|
+
# http://world.std.com/~swmcd/steven/tech/flock.html
|
67
|
+
true
|
68
|
+
end
|
69
|
+
end # class SqliteLock
|
70
|
+
end # module Lock
|
71
|
+
end # module DB
|
72
|
+
end # module Mimi
|
data/lib/mimi/db/lock.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
#
|
2
|
+
# NOTE: mimi/db/lock is NOT required automatically on require "mimi/db"
|
3
|
+
#
|
4
|
+
module Mimi
|
5
|
+
module DB
|
6
|
+
module Lock
|
7
|
+
include Mimi::Core::Module
|
8
|
+
|
9
|
+
default_options(
|
10
|
+
default_lock_options: {
|
11
|
+
# nil -- wait indefinitely
|
12
|
+
# 0 -- do not wait
|
13
|
+
# <float> -- wait number of seconds
|
14
|
+
timeout: nil
|
15
|
+
}
|
16
|
+
)
|
17
|
+
|
18
|
+
def self.module_path
|
19
|
+
Pathname.new(__dir__).join('lock')
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.configure(*)
|
23
|
+
super
|
24
|
+
Mimi::DB.extend(self)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.start
|
28
|
+
require_relative 'lock/postgresql_lock'
|
29
|
+
require_relative 'lock/mysql_lock'
|
30
|
+
require_relative 'lock/sqlite_lock'
|
31
|
+
super
|
32
|
+
end
|
33
|
+
|
34
|
+
# Obtains a named lock
|
35
|
+
#
|
36
|
+
# @param [String,Symbol] name
|
37
|
+
# @param [Hash] opts
|
38
|
+
# @option opts [Numeric,nil] :timeout Timeout in seconds
|
39
|
+
# @option opts [Boolean] :temporary Remove the lock
|
40
|
+
#
|
41
|
+
# @return [true] if the lock was obtained and the block executed
|
42
|
+
# @return [Falsey] if the lock was NOT obtained
|
43
|
+
#
|
44
|
+
def lock(name, opts = {}, &block)
|
45
|
+
lock!(name, opts, &block)
|
46
|
+
true
|
47
|
+
rescue NotAvailable
|
48
|
+
nil
|
49
|
+
end
|
50
|
+
|
51
|
+
def lock!(name, opts = {}, &block)
|
52
|
+
opts = Mimi::DB::Lock.module_options[:default_lock_options].merge(opts.dup)
|
53
|
+
adapter_name = ActiveRecord::Base.connection.adapter_name.downcase.to_sym
|
54
|
+
case adapter_name
|
55
|
+
when :postgresql, :empostgresql, :postgis
|
56
|
+
Mimi::DB::Lock::PostgresqlLock.new(name, opts).execute(&block)
|
57
|
+
when :mysql, :mysql2
|
58
|
+
Mimi::DB::Lock::MysqlLock.new(name, opts).execute(&block)
|
59
|
+
when :sqlite
|
60
|
+
Mimi::DB::Lock::SqliteLock.new(name, opts).execute(&block)
|
61
|
+
else
|
62
|
+
raise "Named locks not supported by the adapter: #{adapter_name}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Lock was not acquired error
|
67
|
+
#
|
68
|
+
class NotAvailable < RuntimeError
|
69
|
+
end # class Error
|
70
|
+
#
|
71
|
+
end # module Lock
|
72
|
+
end # module DB
|
73
|
+
end # module Mimi
|
data/lib/mimi/db/version.rb
CHANGED
data/lib/mimi/db.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
require 'mimi/core'
|
2
2
|
require 'active_record'
|
3
3
|
require 'mini_record'
|
4
|
-
require 'with_advisory_lock'
|
5
4
|
|
6
5
|
module Mimi
|
7
6
|
module DB
|
@@ -92,32 +91,10 @@ module Mimi
|
|
92
91
|
reaping_frequency: 15
|
93
92
|
}.stringify_keys
|
94
93
|
end
|
95
|
-
|
96
|
-
def self.models
|
97
|
-
ActiveRecord::Base.descendants
|
98
|
-
end
|
99
|
-
|
100
|
-
def self.migrate_schema!
|
101
|
-
models.each(&:auto_upgrade!)
|
102
|
-
end
|
103
|
-
|
104
|
-
def self.create!
|
105
|
-
ActiveRecord::Tasks::DatabaseTasks.root = Mimi.app_root_path
|
106
|
-
ActiveRecord::Tasks::DatabaseTasks.create(active_record_config)
|
107
|
-
end
|
108
|
-
|
109
|
-
def self.drop!
|
110
|
-
ActiveRecord::Tasks::DatabaseTasks.root = Mimi.app_root_path
|
111
|
-
ActiveRecord::Tasks::DatabaseTasks.drop(active_record_config)
|
112
|
-
end
|
113
|
-
|
114
|
-
def self.clear!
|
115
|
-
ActiveRecord::Tasks::DatabaseTasks.root = Mimi.app_root_path
|
116
|
-
ActiveRecord::Tasks::DatabaseTasks.purge(active_record_config)
|
117
|
-
end
|
118
94
|
end # module DB
|
119
95
|
end # module Mimi
|
120
96
|
|
121
97
|
require_relative 'db/version'
|
122
98
|
require_relative 'db/extensions'
|
99
|
+
require_relative 'db/helpers'
|
123
100
|
require_relative 'db/foreign_key'
|
data/mimi-db.gemspec
CHANGED
@@ -30,7 +30,6 @@ Gem::Specification.new do |spec|
|
|
30
30
|
spec.add_dependency 'mimi-core', '~> 0.1'
|
31
31
|
spec.add_dependency 'activerecord', '~> 4.2'
|
32
32
|
spec.add_dependency 'mini_record', '~> 0.4'
|
33
|
-
spec.add_dependency 'with_advisory_lock', '~> 3.0'
|
34
33
|
|
35
34
|
spec.add_development_dependency 'bundler', '~> 1.11'
|
36
35
|
spec.add_development_dependency 'rake', '~> 10.0'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mimi-db
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alex Kukushkin
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-05-
|
11
|
+
date: 2016-05-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mimi-core
|
@@ -52,20 +52,6 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0.4'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: with_advisory_lock
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - "~>"
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '3.0'
|
62
|
-
type: :runtime
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - "~>"
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '3.0'
|
69
55
|
- !ruby/object:Gem::Dependency
|
70
56
|
name: bundler
|
71
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -142,6 +128,11 @@ files:
|
|
142
128
|
- lib/mimi/db.rb
|
143
129
|
- lib/mimi/db/extensions.rb
|
144
130
|
- lib/mimi/db/foreign_key.rb
|
131
|
+
- lib/mimi/db/helpers.rb
|
132
|
+
- lib/mimi/db/lock.rb
|
133
|
+
- lib/mimi/db/lock/mysql_lock.rb
|
134
|
+
- lib/mimi/db/lock/postgresql_lock.rb
|
135
|
+
- lib/mimi/db/lock/sqlite_lock.rb
|
145
136
|
- lib/mimi/db/version.rb
|
146
137
|
- lib/tasks/db.rake
|
147
138
|
- mimi-db.gemspec
|