makara 0.3.5

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.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.rspec +2 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +28 -0
  7. data/CHANGELOG.md +27 -0
  8. data/Gemfile +18 -0
  9. data/LICENSE.txt +22 -0
  10. data/README.md +278 -0
  11. data/Rakefile +9 -0
  12. data/gemfiles/ar30.gemfile +30 -0
  13. data/gemfiles/ar31.gemfile +29 -0
  14. data/gemfiles/ar32.gemfile +29 -0
  15. data/gemfiles/ar40.gemfile +15 -0
  16. data/gemfiles/ar41.gemfile +15 -0
  17. data/gemfiles/ar42.gemfile +15 -0
  18. data/lib/active_record/connection_adapters/jdbcmysql_makara_adapter.rb +25 -0
  19. data/lib/active_record/connection_adapters/jdbcpostgresql_makara_adapter.rb +25 -0
  20. data/lib/active_record/connection_adapters/makara_abstract_adapter.rb +209 -0
  21. data/lib/active_record/connection_adapters/makara_jdbcmysql_adapter.rb +25 -0
  22. data/lib/active_record/connection_adapters/makara_jdbcpostgresql_adapter.rb +25 -0
  23. data/lib/active_record/connection_adapters/makara_mysql2_adapter.rb +44 -0
  24. data/lib/active_record/connection_adapters/makara_postgresql_adapter.rb +44 -0
  25. data/lib/active_record/connection_adapters/mysql2_makara_adapter.rb +44 -0
  26. data/lib/active_record/connection_adapters/postgresql_makara_adapter.rb +44 -0
  27. data/lib/makara.rb +25 -0
  28. data/lib/makara/cache.rb +53 -0
  29. data/lib/makara/cache/memory_store.rb +28 -0
  30. data/lib/makara/cache/noop_store.rb +15 -0
  31. data/lib/makara/config_parser.rb +200 -0
  32. data/lib/makara/connection_wrapper.rb +170 -0
  33. data/lib/makara/context.rb +46 -0
  34. data/lib/makara/error_handler.rb +39 -0
  35. data/lib/makara/errors/all_connections_blacklisted.rb +13 -0
  36. data/lib/makara/errors/blacklist_connection.rb +14 -0
  37. data/lib/makara/errors/no_connections_available.rb +14 -0
  38. data/lib/makara/logging/logger.rb +23 -0
  39. data/lib/makara/logging/subscriber.rb +38 -0
  40. data/lib/makara/middleware.rb +109 -0
  41. data/lib/makara/pool.rb +188 -0
  42. data/lib/makara/proxy.rb +277 -0
  43. data/lib/makara/railtie.rb +14 -0
  44. data/lib/makara/version.rb +15 -0
  45. data/makara.gemspec +19 -0
  46. data/spec/active_record/connection_adapters/makara_abstract_adapter_error_handling_spec.rb +92 -0
  47. data/spec/active_record/connection_adapters/makara_abstract_adapter_spec.rb +114 -0
  48. data/spec/active_record/connection_adapters/makara_mysql2_adapter_spec.rb +183 -0
  49. data/spec/active_record/connection_adapters/makara_postgresql_adapter_spec.rb +121 -0
  50. data/spec/cache_spec.rb +59 -0
  51. data/spec/config_parser_spec.rb +102 -0
  52. data/spec/connection_wrapper_spec.rb +33 -0
  53. data/spec/context_spec.rb +107 -0
  54. data/spec/middleware_spec.rb +84 -0
  55. data/spec/pool_spec.rb +158 -0
  56. data/spec/proxy_spec.rb +182 -0
  57. data/spec/spec_helper.rb +46 -0
  58. data/spec/support/configurator.rb +13 -0
  59. data/spec/support/deep_dup.rb +12 -0
  60. data/spec/support/mock_objects.rb +67 -0
  61. data/spec/support/mysql2_database.yml +17 -0
  62. data/spec/support/mysql2_database_with_custom_errors.yml +17 -0
  63. data/spec/support/pool_extensions.rb +14 -0
  64. data/spec/support/postgresql_database.yml +13 -0
  65. data/spec/support/proxy_extensions.rb +33 -0
  66. data/spec/support/schema.rb +7 -0
  67. metadata +144 -0
@@ -0,0 +1,44 @@
1
+ require 'active_record/connection_adapters/makara_abstract_adapter'
2
+ require 'active_record/connection_adapters/postgresql_adapter'
3
+
4
+ if ActiveRecord::VERSION::MAJOR >= 4
5
+
6
+ module ActiveRecord
7
+ module ConnectionHandling
8
+ def makara_postgresql_connection(config)
9
+ ActiveRecord::ConnectionAdapters::MakaraPostgreSQLAdapter.new(config)
10
+ end
11
+ end
12
+ end
13
+
14
+ else
15
+
16
+ module ActiveRecord
17
+ class Base
18
+ def self.makara_postgresql_connection(config)
19
+ ActiveRecord::ConnectionAdapters::MakaraPostgreSQLAdapter.new(config)
20
+ end
21
+ end
22
+ end
23
+
24
+ end
25
+
26
+ module ActiveRecord
27
+ module ConnectionAdapters
28
+ class MakaraPostgreSQLAdapter < ActiveRecord::ConnectionAdapters::MakaraAbstractAdapter
29
+
30
+ class << self
31
+ def visitor_for(*args)
32
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.visitor_for(*args)
33
+ end
34
+ end
35
+
36
+ protected
37
+
38
+ def active_record_connection_for(config)
39
+ ::ActiveRecord::Base.postgresql_connection(config)
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,44 @@
1
+ require 'active_record/connection_adapters/makara_abstract_adapter'
2
+ require 'active_record/connection_adapters/mysql2_adapter'
3
+
4
+ if ActiveRecord::VERSION::MAJOR >= 4
5
+
6
+ module ActiveRecord
7
+ module ConnectionHandling
8
+ def mysql2_makara_connection(config)
9
+ ActiveRecord::ConnectionAdapters::MakaraMysql2Adapter.new(config)
10
+ end
11
+ end
12
+ end
13
+
14
+ else
15
+
16
+ module ActiveRecord
17
+ class Base
18
+ def self.mysql2_makara_connection(config)
19
+ ActiveRecord::ConnectionAdapters::MakaraMysql2Adapter.new(config)
20
+ end
21
+ end
22
+ end
23
+
24
+ end
25
+
26
+ module ActiveRecord
27
+ module ConnectionAdapters
28
+ class MakaraMysql2Adapter < ActiveRecord::ConnectionAdapters::MakaraAbstractAdapter
29
+
30
+ class << self
31
+ def visitor_for(*args)
32
+ ActiveRecord::ConnectionAdapters::Mysql2Adapter.visitor_for(*args)
33
+ end
34
+ end
35
+
36
+ protected
37
+
38
+ def active_record_connection_for(config)
39
+ ::ActiveRecord::Base.mysql2_connection(config)
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,44 @@
1
+ require 'active_record/connection_adapters/makara_abstract_adapter'
2
+ require 'active_record/connection_adapters/postgresql_adapter'
3
+
4
+ if ActiveRecord::VERSION::MAJOR >= 4
5
+
6
+ module ActiveRecord
7
+ module ConnectionHandling
8
+ def postgresql_makara_connection(config)
9
+ ActiveRecord::ConnectionAdapters::MakaraPostgreSQLAdapter.new(config)
10
+ end
11
+ end
12
+ end
13
+
14
+ else
15
+
16
+ module ActiveRecord
17
+ class Base
18
+ def self.postgresql_makara_connection(config)
19
+ ActiveRecord::ConnectionAdapters::MakaraPostgreSQLAdapter.new(config)
20
+ end
21
+ end
22
+ end
23
+
24
+ end
25
+
26
+ module ActiveRecord
27
+ module ConnectionAdapters
28
+ class MakaraPostgreSQLAdapter < ActiveRecord::ConnectionAdapters::MakaraAbstractAdapter
29
+
30
+ class << self
31
+ def visitor_for(*args)
32
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.visitor_for(*args)
33
+ end
34
+ end
35
+
36
+ protected
37
+
38
+ def active_record_connection_for(config)
39
+ ::ActiveRecord::Base.postgresql_connection(config)
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,25 @@
1
+ require 'makara/version'
2
+ require 'makara/railtie' if defined?(Rails)
3
+ module Makara
4
+
5
+ autoload :Cache, 'makara/cache'
6
+ autoload :ConfigParser, 'makara/config_parser'
7
+ autoload :ConnectionWrapper, 'makara/connection_wrapper'
8
+ autoload :Context, 'makara/context'
9
+ autoload :ErrorHandler, 'makara/error_handler'
10
+ autoload :Middleware, 'makara/middleware'
11
+ autoload :Pool, 'makara/pool'
12
+ autoload :Proxy, 'makara/proxy'
13
+
14
+ module Errors
15
+ autoload :AllConnectionsBlacklisted, 'makara/errors/all_connections_blacklisted'
16
+ autoload :BlacklistConnection, 'makara/errors/blacklist_connection'
17
+ autoload :NoConnectionsAvailable, 'makara/errors/no_connections_available'
18
+ end
19
+
20
+ module Logging
21
+ autoload :Logger, 'makara/logging/logger'
22
+ autoload :Subscriber, 'makara/logging/subscriber'
23
+ end
24
+
25
+ end
@@ -0,0 +1,53 @@
1
+ require 'active_support/core_ext/object/try'
2
+
3
+ # The Makara Cache should have access to your centralized cache store.
4
+ # It serves the purpose of storing the Makara::Context across requests, servers, etc.
5
+
6
+ module Makara
7
+ module Cache
8
+
9
+ autoload :MemoryStore, 'makara/cache/memory_store'
10
+ autoload :NoopStore, 'makara/cache/noop_store'
11
+
12
+ class << self
13
+
14
+ def store=(store)
15
+ @store = store
16
+ end
17
+
18
+ def read(key)
19
+ store.try(:read, key)
20
+ end
21
+
22
+ def write(key, value, ttl)
23
+ store.try(:write, key, value, :expires_in => ttl.to_i)
24
+ end
25
+
26
+ protected
27
+
28
+ def store
29
+ case @store
30
+ when :noop, :null
31
+ @store = Makara::Cache::NoopStore.new
32
+ when :memory
33
+ @store = Makara::Cache::MemoryStore.new
34
+ else
35
+ if defined?(Rails)
36
+
37
+ # in AR3 RAILS_CACHE may not be loaded if the full env is not present
38
+ # Rails.cache will throw an error because of it.
39
+ if ActiveRecord::VERSION::MAJOR < 4
40
+ @store ||= Rails.cache if defined?(RAILS_CACHE)
41
+ else
42
+ @store ||= Rails.cache if defined?(Rails)
43
+ end
44
+ end
45
+ end
46
+
47
+ @store
48
+ end
49
+
50
+ end
51
+
52
+ end
53
+ end
@@ -0,0 +1,28 @@
1
+ module Makara
2
+ module Cache
3
+ class MemoryStore
4
+
5
+ def initialize
6
+ @data = {}
7
+ end
8
+
9
+ def read(key)
10
+ clean
11
+ @data[key].try(:[], 0)
12
+ end
13
+
14
+ def write(key, value, options = {})
15
+ clean
16
+ @data[key] = [value, Time.now.to_i + (options[:expires_in] || 5).to_i]
17
+ true
18
+ end
19
+
20
+ protected
21
+
22
+ def clean
23
+ @data.delete_if{|k,v| v[1] <= Time.now.to_i }
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,15 @@
1
+ module Makara
2
+ module Cache
3
+ class NoopStore
4
+
5
+ def read(key)
6
+ nil
7
+ end
8
+
9
+ def write(key, value, options = {})
10
+ nil
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,200 @@
1
+ require 'digest/md5'
2
+ require 'active_support/core_ext/hash/keys'
3
+ require 'active_support/core_ext/hash/except'
4
+
5
+ # Convenience methods to grab subconfigs out of the primary configuration.
6
+ # Provides a way to generate a consistent ID based on a unique config.
7
+ # Makara configs should be formatted like so:
8
+ # --
9
+ # top_level: 'variable'
10
+ # another: 'top level variable'
11
+ # makara:
12
+ # master_ttl: 3
13
+ # blacklist_duration: 20
14
+ # connections:
15
+ # - role: 'master'
16
+ # - role: 'slave'
17
+ # - role: 'slave'
18
+ # name: 'slave2'
19
+
20
+ module Makara
21
+ class ConfigParser
22
+
23
+ DEFAULTS = {
24
+ :master_ttl => 5,
25
+ :blacklist_duration => 30,
26
+ :sticky => true
27
+ }
28
+
29
+ # ConnectionUrlResolver is borrowed from Rails 4-2 since its location and implementation
30
+ # vary slightly among Rails versions, but the behavior is the same. Thus, borrowing the
31
+ # class should be the most future-safe way to parse a database url.
32
+ #
33
+ # Expands a connection string into a hash.
34
+ class ConnectionUrlResolver # :nodoc:
35
+
36
+ # == Example
37
+ #
38
+ # url = "postgresql://foo:bar@localhost:9000/foo_test?pool=5&timeout=3000"
39
+ # ConnectionUrlResolver.new(url).to_hash
40
+ # # => {
41
+ # "adapter" => "postgresql",
42
+ # "host" => "localhost",
43
+ # "port" => 9000,
44
+ # "database" => "foo_test",
45
+ # "username" => "foo",
46
+ # "password" => "bar",
47
+ # "pool" => "5",
48
+ # "timeout" => "3000"
49
+ # }
50
+ def initialize(url)
51
+ raise "Database URL cannot be empty" if url.blank?
52
+ @uri = URI.parse(url)
53
+ @adapter = @uri.scheme.tr('-', '_')
54
+ @adapter = "postgresql" if @adapter == "postgres"
55
+
56
+ if @uri.opaque
57
+ @uri.opaque, @query = @uri.opaque.split('?', 2)
58
+ else
59
+ @query = @uri.query
60
+ end
61
+ end
62
+
63
+ # Converts the given URL to a full connection hash.
64
+ def to_hash
65
+ config = raw_config.reject { |_,value| value.blank? }
66
+ config.map { |key,value| config[key] = URI.unescape(value) if value.is_a? String }
67
+ config
68
+ end
69
+
70
+ private
71
+
72
+ def uri
73
+ @uri
74
+ end
75
+
76
+ # Converts the query parameters of the URI into a hash.
77
+ #
78
+ # "localhost?pool=5&reaping_frequency=2"
79
+ # # => { "pool" => "5", "reaping_frequency" => "2" }
80
+ #
81
+ # returns empty hash if no query present.
82
+ #
83
+ # "localhost"
84
+ # # => {}
85
+ def query_hash
86
+ Hash[(@query || '').split("&").map { |pair| pair.split("=") }]
87
+ end
88
+
89
+ def raw_config
90
+ if uri.opaque
91
+ query_hash.merge({
92
+ "adapter" => @adapter,
93
+ "database" => uri.opaque })
94
+ else
95
+ query_hash.merge({
96
+ "adapter" => @adapter,
97
+ "username" => uri.user,
98
+ "password" => uri.password,
99
+ "port" => uri.port,
100
+ "database" => database_from_path,
101
+ "host" => uri.host })
102
+ end
103
+ end
104
+
105
+ # Returns name of the database.
106
+ def database_from_path
107
+ if @adapter == 'sqlite3'
108
+ # 'sqlite3:/foo' is absolute, because that makes sense. The
109
+ # corresponding relative version, 'sqlite3:foo', is handled
110
+ # elsewhere, as an "opaque".
111
+
112
+ uri.path
113
+ else
114
+ # Only SQLite uses a filename as the "database" name; for
115
+ # anything else, a leading slash would be silly.
116
+
117
+ uri.path.sub(%r{^/}, "")
118
+ end
119
+ end
120
+ end
121
+
122
+ # NOTE: url format must be, e.g.
123
+ # url: mysql2://...
124
+ # NOT
125
+ # url: mysql2_makara://...
126
+ # since the '_' in the protocol (mysql2_makara) makes the URI invalid
127
+ # NOTE: Does not use ENV['DATABASE_URL']
128
+ def self.merge_and_resolve_default_url_config(config)
129
+ if ENV['DATABASE_URL']
130
+ Logging::Logger.log "Please rename DATABASE_URL to use in the database.yml", :warn
131
+ end
132
+ return config unless config.key?(:url)
133
+ url = config[:url]
134
+ url_config = ConnectionUrlResolver.new(url).to_hash
135
+ url_config = url_config.symbolize_keys
136
+ url_config.delete(:adapter)
137
+ config.delete(:url)
138
+ config.update(url_config)
139
+ end
140
+
141
+ attr_reader :makara_config
142
+
143
+ def initialize(config)
144
+ @config = config.symbolize_keys
145
+ @makara_config = DEFAULTS.merge(@config[:makara] || {})
146
+ @makara_config = @makara_config.symbolize_keys
147
+ @id = @makara_config[:id]
148
+ end
149
+
150
+
151
+ def id
152
+ @id ||= begin
153
+ sorted = recursive_sort(@config)
154
+ Digest::MD5.hexdigest(sorted.to_s)
155
+ end
156
+ end
157
+
158
+
159
+ def master_configs
160
+ all_configs.
161
+ select{|config| config[:role] == 'master' }.
162
+ map{|config| config.except(:role) }
163
+ end
164
+
165
+
166
+ def slave_configs
167
+ all_configs.
168
+ reject{|config| config[:role] == 'master' }.
169
+ map{|config| config.except(:role) }
170
+ end
171
+
172
+
173
+ protected
174
+
175
+
176
+ def all_configs
177
+ @makara_config[:connections].map do |connection|
178
+ base_config.merge(connection.symbolize_keys)
179
+ end
180
+ end
181
+
182
+
183
+ def base_config
184
+ @base_config ||= DEFAULTS.merge(@config).except(:makara)
185
+ end
186
+
187
+
188
+ def recursive_sort(thing)
189
+ return thing.to_s unless thing.include?(Enumerable)
190
+
191
+ thing.map do |part|
192
+ recursive_sort(part)
193
+ end
194
+
195
+ thing.sort_by(&:to_s)
196
+
197
+ end
198
+
199
+ end
200
+ end