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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8c53122867fbeeebb2deb1484c383ecf659b4d3c
4
- data.tar.gz: 6202ee4865efc7d314125eca122c30d797d71706
3
+ metadata.gz: 9aec6438056a412a0d1807eb550ecae64eea936e
4
+ data.tar.gz: 7abc14cb1ee6040f9af0f5dc0852fd464c5a1f2e
5
5
  SHA512:
6
- metadata.gz: 478721eb8fe5faf6dbe3144b939bf2a925e76e0a647e0b58f1a569e01dc7d0d012c0db4df858511a39f713fca0ed29b82f21024c240029fe52a49021792c1151
7
- data.tar.gz: b82fa58eb333e264e804b3bdf14e94474856758f2e9df6fba1f0659b2f8f3a42af0a2d118003b15a8337b551adc89e503c19e04277273107452fe565f85be2a3
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
@@ -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
@@ -1,5 +1,5 @@
1
1
  module Mimi
2
2
  module DB
3
- VERSION = '0.1.2'.freeze
3
+ VERSION = '0.1.3'.freeze
4
4
  end
5
5
  end
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.2
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-12 00:00:00.000000000 Z
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