mimi-db 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
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