switchman 1.9.0 → 1.10.0

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: d2e63c8b9d8a43c1cf8716f775593bc0e5c7296b
4
- data.tar.gz: c4cda98c0bbdf18d0c648adcf8d8465c90f2246e
3
+ metadata.gz: d9bf082be6cc36e99b6be46bdf90c47102c45962
4
+ data.tar.gz: 105fd5544f11577cd939de766a0d2950f727759e
5
5
  SHA512:
6
- metadata.gz: f2e7e0889dce714d0d128eeca5b09434ba0cd313ebe9e09db2f1d58e7d706e6aa1353e03b54496d2f145fbd19467c74bd095870025eb6fca0321170c06ffd14a
7
- data.tar.gz: e0614890e323dcedd8b272e1ec571be80de1640d27601d9c905208e4fca69931676ec03e4ef038775aa634e43751909bdf0b18f261fe32b1c8a394764c830d24
6
+ metadata.gz: 3a69a13d8922e15c57a972c4521833f0d1658d7c13198f766e1cc2374a3cc8781765984194bb1b57950b32bc2c52221b3aeb8d1e6474e30bde3a4aa30a6d8cd3
7
+ data.tar.gz: a22298dbbc075b8a69af90bd87c97a74ad598729551f268a7bd3bb9eae08aa28eec6ca1654bf35b2b7863527acd2aab63458a8ad439453d4d86fb5514c0d7447
@@ -8,4 +8,4 @@ ActiveRecord::Base
8
8
  # we need to make sure we define the class someone wanted (in which case
9
9
  # AR's on_load hook won't be called either yet, and establish_connection
10
10
  # will be safe)
11
- require 'switchman/shard_internal'
11
+ require_dependency 'switchman/shard_internal'
@@ -26,6 +26,7 @@ module Switchman
26
26
  validates_uniqueness_of :default, :if => lambda { |s| s.default? }
27
27
 
28
28
  after_save :clear_cache
29
+ after_destroy :clear_cache
29
30
 
30
31
  scope :primary, -> { where(name: nil).order(:database_server_id, :id).distinct_on(:database_server_id) }
31
32
 
@@ -191,14 +192,14 @@ module Switchman
191
192
  max_procs = determine_max_procs(options.delete(:max_procs), parallel)
192
193
  if ::ActiveRecord::Relation === scope
193
194
  # still need a post-uniq, cause the default database server could be NULL or Rails.env in the db
194
- database_servers = scope.reorder('database_server_id').select(:database_server_id).uniq.
195
+ database_servers = scope.reorder('database_server_id').select(:database_server_id).distinct.
195
196
  map(&:database_server).compact.uniq
196
197
  parallel = [(max_procs.to_f / database_servers.count).ceil, parallel].min if max_procs
197
198
 
198
199
  scopes = Hash[database_servers.map do |server|
199
200
  server_scope = server.shards.merge(scope)
200
201
  if parallel == 1
201
- subscopes = server_scope
202
+ subscopes = [server_scope]
202
203
  else
203
204
  subscopes = []
204
205
  total = server_scope.count
@@ -223,6 +224,8 @@ module Switchman
223
224
  scopes = Hash[scopes.map do |(server, shards)|
224
225
  [server, shards.in_groups(parallel, false).compact]
225
226
  end]
227
+ else
228
+ scopes = Hash[scopes.map { |(server, shards)| [server, [shards]] }]
226
229
  end
227
230
  end
228
231
 
@@ -250,7 +253,7 @@ module Switchman
250
253
 
251
254
  # only one process; don't bother forking
252
255
  if scopes.length == 1 && parallel == 1
253
- return with_each_shard(scopes.first.last, categories, options) { yield }
256
+ return with_each_shard(scopes.first.last.first, categories, options) { yield }
254
257
  end
255
258
 
256
259
  # clear connections prior to forking (no more queries will be executed in the parent,
@@ -259,10 +262,6 @@ module Switchman
259
262
  ::ActiveRecord::Base.clear_all_connections!
260
263
 
261
264
  scopes.each do |server, subscopes|
262
- if !(::ActiveRecord::Relation === subscopes.first) && subscopes.first.class != Array
263
- subscopes = [subscopes]
264
- end
265
-
266
265
  subscopes.each_with_index do |subscope, idx|
267
266
  if subscopes.length > 1
268
267
  name = "#{server.id} #{idx + 1}"
@@ -274,7 +273,7 @@ module Switchman
274
273
  exception_pipes << exception_pipe
275
274
  pid, io_in, io_out, io_err = Open4.pfork4(lambda do
276
275
  begin
277
- Switchman.config[:on_fork_proc].try(:call)
276
+ Switchman.config[:on_fork_proc]&.call
278
277
  $0 = [$0, ARGV, name].flatten.join(' ')
279
278
  with_each_shard(subscope, categories, options) { yield }
280
279
  exception_pipe.last.close
@@ -349,8 +348,7 @@ module Switchman
349
348
  previous_shard = nil
350
349
  close_connections_if_needed = lambda do |shard|
351
350
  # prune the prior connection unless it happened to be the same
352
- if previous_shard && shard != previous_shard &&
353
- (shard.database_server != previous_shard.database_server || !previous_shard.database_server.shareable?)
351
+ if previous_shard && shard != previous_shard && !previous_shard.database_server.shareable?
354
352
  previous_shard.activate do
355
353
  ::Shackles.activated_environments.each do |env|
356
354
  ::Shackles.activate(env) do
@@ -610,7 +608,7 @@ module Switchman
610
608
  begin
611
609
  adapter = self.database_server.config[:adapter]
612
610
  sharding_config = Switchman.config || {}
613
- drop_statement = sharding_config[adapter].try(:[], :drop_statement)
611
+ drop_statement = sharding_config[adapter]&.[](:drop_statement)
614
612
  drop_statement ||= sharding_config[:drop_statement]
615
613
  if drop_statement
616
614
  drop_statement = Array(drop_statement).dup.
@@ -1,4 +1,4 @@
1
- class CreateSwitchmanShards < ActiveRecord::Migration
1
+ class CreateSwitchmanShards < ActiveRecord::Migration[4.2]
2
2
  def change
3
3
  create_table :switchman_shards do |t|
4
4
  t.string :name
@@ -1,4 +1,4 @@
1
- class CreateDefaultShard < ActiveRecord::Migration
1
+ class CreateDefaultShard < ActiveRecord::Migration[4.2]
2
2
  def up
3
3
  unless Switchman::Shard.default.is_a?(Switchman::Shard)
4
4
  Switchman::Shard.reset_column_information
@@ -1,4 +1,4 @@
1
- class AddBackDefaultStringLimitsSwitchman < ActiveRecord::Migration
1
+ class AddBackDefaultStringLimitsSwitchman < ActiveRecord::Migration[4.2]
2
2
  def up
3
3
  add_string_limit_if_missing :switchman_shards, :name
4
4
  add_string_limit_if_missing :switchman_shards, :database_server_id
@@ -38,6 +38,14 @@ module Switchman
38
38
  ensure
39
39
  @last_query_at = Time.now
40
40
  end
41
+
42
+ private
43
+
44
+ def id_value_for_database(value)
45
+ return super unless value.class.sharded_primary_key?
46
+ # do this the Rails 4.2 way, so that if Shard.current != self.shard, the id gets transposed
47
+ quote(value.id)
48
+ end
41
49
  end
42
50
  end
43
51
  end
@@ -36,14 +36,17 @@ module Switchman
36
36
  end
37
37
 
38
38
  module CollectionAssociation
39
- def get_records
40
- shards = reflection.options[:multishard] && owner.respond_to?(:associated_shards) ? owner.associated_shards : [shard]
41
- # activate both the owner and the target's shard category, so that Reflection#join_id_for,
42
- # when called for the owner, will be returned relative to shard the query will execute on
43
- Shard.with_each_shard(shards, [klass.shard_category, owner.class.shard_category].uniq) do
44
- super
39
+ method = ::Rails.version < '5.1' ? :get_records : :find_target
40
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
41
+ def #{method}
42
+ shards = reflection.options[:multishard] && owner.respond_to?(:associated_shards) ? owner.associated_shards : [shard]
43
+ # activate both the owner and the target's shard category, so that Reflection#join_id_for,
44
+ # when called for the owner, will be returned relative to shard the query will execute on
45
+ Shard.with_each_shard(shards, [klass.shard_category, owner.class.shard_category].uniq) do
46
+ super
47
+ end
45
48
  end
46
- end
49
+ RUBY
47
50
  end
48
51
 
49
52
  module BelongsToAssociation
@@ -169,6 +172,12 @@ module Switchman
169
172
  end
170
173
 
171
174
  module CollectionProxy
175
+ def initialize(*args)
176
+ super
177
+ self.shard_value = scope.shard_value
178
+ self.shard_source_value = :association
179
+ end
180
+
172
181
  def shard(*args)
173
182
  scope.shard(*args)
174
183
  end
@@ -27,7 +27,7 @@ module Switchman
27
27
  def reflection_for_integer_attribute(attr_name)
28
28
  attr_name = attr_name.to_s
29
29
  columns_hash[attr_name] && columns_hash[attr_name].type == :integer &&
30
- reflections.find { |_, r| r.belongs_to? && r.foreign_key.to_s == attr_name }.try(:last)
30
+ reflections.find { |_, r| r.belongs_to? && r.foreign_key.to_s == attr_name }&.last
31
31
  rescue ::ActiveRecord::StatementInvalid
32
32
  # this is for when models are referenced in initializers before migrations have been run
33
33
  raise if connection.open_transactions > 0
@@ -65,8 +65,8 @@ module Switchman
65
65
  if reflection
66
66
  if reflection.options[:polymorphic]
67
67
  # a polymorphic association has to be discovered at runtime. This code ends up being something like
68
- # context_type.try(:constantize).try(:shard_category) || :primary
69
- "read_attribute(:#{reflection.foreign_type}).try(:constantize).try(:shard_category) || :primary"
68
+ # context_type.&.constantize&.shard_category || :primary
69
+ "read_attribute(:#{reflection.foreign_type})&.constantize&.shard_category || :primary"
70
70
  else
71
71
  # otherwise we can just return a symbol for the statically known type of the association
72
72
  reflection.klass.shard_category.inspect
@@ -33,7 +33,7 @@ module Switchman
33
33
 
34
34
  def integral_id?
35
35
  if @integral_id == nil
36
- @integral_id = columns_hash[primary_key].try(:type) == :integer
36
+ @integral_id = columns_hash[primary_key]&.type == :integer
37
37
  end
38
38
  @integral_id
39
39
  end
@@ -105,7 +105,7 @@ module Switchman
105
105
  end
106
106
 
107
107
  def scope_class
108
- self.class.base_class
108
+ ::Rails.version >= '5' ? self.class : self.class.base_class
109
109
  end
110
110
 
111
111
  def save(*args)
@@ -138,7 +138,7 @@ module Switchman
138
138
  end
139
139
 
140
140
  def hash
141
- self.class.sharded_primary_key? ? Shard.global_id_for(id).hash : super
141
+ self.class.sharded_primary_key? ? self.class.hash ^ Shard.global_id_for(id).hash : super
142
142
  end
143
143
 
144
144
  def to_param
@@ -151,6 +151,14 @@ module Switchman
151
151
  @shard_set_in_stone = false
152
152
  copy
153
153
  end
154
+
155
+ if ::Rails.version >= '5'
156
+ def quoted_id
157
+ return super unless self.class.sharded_primary_key?
158
+ # do this the Rails 4.2 way, so that if Shard.current != self.shard, the id gets transposed
159
+ self.class.connection.quote(id)
160
+ end
161
+ end
154
162
  end
155
163
  end
156
164
  end
@@ -40,10 +40,15 @@ module Switchman
40
40
  Shard.default
41
41
 
42
42
  # automatically change config to allow for sharing connections with simple config
43
- ConnectionHandler.make_sharing_automagic(spec.config)
43
+ config = ::Rails.version < '5.1' ? spec.config : pool.spec.config
44
+ ConnectionHandler.make_sharing_automagic(config)
44
45
  ConnectionHandler.make_sharing_automagic(Shard.default.database_server.config)
45
46
 
46
- ::ActiveRecord::Base.configurations[::Rails.env] = spec.instance_variable_get(:@config).stringify_keys
47
+ if ::Rails.version < '5.1'
48
+ ::ActiveRecord::Base.configurations[::Rails.env] = spec.instance_variable_get(:@config).stringify_keys
49
+ else
50
+ ::ActiveRecord::Base.configurations[::Rails.env] = config.stringify_keys
51
+ end
47
52
  end
48
53
 
49
54
  @shard_connection_pools ||= { [:master, Shard.default.database_server.shareable? ? ::Rails.env : Shard.default] => pool}
@@ -162,12 +167,13 @@ module Switchman
162
167
  # A connection was established in an ancestor process that must have
163
168
  # subsequently forked. We can't reuse the connection, but we can copy
164
169
  # the specification and establish a new connection with it.
165
- pool = nil
166
- if ancestor_pool.is_a?(ConnectionPoolProxy)
167
- pool = establish_connection ancestor_pool.default_pool.spec
170
+ spec = if ancestor_pool.is_a?(ConnectionPoolProxy)
171
+ ancestor_pool.default_pool.spec
168
172
  else
169
- pool = establish_connection ancestor_pool.spec
173
+ ancestor_pool.spec
170
174
  end
175
+ spec = spec.to_hash if ::Rails.version >= '5.1'
176
+ pool = establish_connection spec
171
177
  pool.instance_variable_set(:@schema_cache, ancestor_pool.schema_cache) if ancestor_pool.schema_cache
172
178
  pool
173
179
  elsif spec_name != "primary"
@@ -17,13 +17,16 @@ module Switchman
17
17
  shard = " [#{shard[:database_server_id]}:#{shard[:id]} #{shard[:env]}]" if shard
18
18
 
19
19
  unless (payload[:binds] || []).empty?
20
- binds = " " + payload[:binds].map { |col,v|
21
- if col
22
- [col.name, v]
23
- else
24
- [nil, v]
25
- end
26
- }.inspect
20
+ if ::Rails.version < '5'
21
+ binds = " " + payload[:binds].map { |col,v|
22
+ render_bind(col, v)
23
+ }.inspect
24
+ else
25
+ casted_params = type_casted_binds(payload[:binds], payload[:type_casted_binds])
26
+ binds = " " + payload[:binds].zip(casted_params).map { |attr, value|
27
+ render_bind(attr, value)
28
+ }.inspect
29
+ end
27
30
  end
28
31
 
29
32
  if ::Rails.version >= '5'
@@ -0,0 +1,37 @@
1
+ module Switchman
2
+ module ActiveRecord
3
+ module Migration
4
+ module Compatibility
5
+ module V5_0
6
+ def create_table(*args, **options)
7
+ unless options.key?(:id)
8
+ options[:id] = :bigserial
9
+ end
10
+ if block_given?
11
+ super do |td|
12
+ yield td
13
+ end
14
+ else
15
+ super
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ def connection
22
+ conn = super
23
+ if conn.shard != ::ActiveRecord::Base.connection_pool.current_pool.shard
24
+ ::ActiveRecord::Base.connection_pool.current_pool.switch_database(conn)
25
+ end
26
+ conn
27
+ end
28
+ end
29
+
30
+ module Migrator
31
+ def generate_migrator_advisory_lock_id
32
+ shard_name_hash = Zlib.crc32(Shard.current.name)
33
+ ::ActiveRecord::Migrator::MIGRATOR_SALT * shard_name_hash
34
+ end
35
+ end
36
+ end
37
+ end
@@ -1,8 +1,10 @@
1
1
  module Switchman
2
2
  module ActiveRecord
3
3
  module PostgreSQLAdapter
4
- def self.prepended(klass)
5
- klass::NATIVE_DATABASE_TYPES[:primary_key] = "bigserial primary key".freeze
4
+ if ::Rails.version < '5'
5
+ def self.prepended(klass)
6
+ klass::NATIVE_DATABASE_TYPES[:primary_key] = "bigserial primary key".freeze
7
+ end
6
8
  end
7
9
 
8
10
  # copy/paste; use quote_local_table_name
@@ -137,7 +139,7 @@ module Switchman
137
139
  end.compact
138
140
  end
139
141
 
140
- def index_name_exists?(table_name, index_name, default)
142
+ def index_name_exists?(table_name, index_name, _default = nil)
141
143
  schema = shard.name if use_qualified_names?
142
144
 
143
145
  exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
@@ -205,6 +207,12 @@ module Switchman
205
207
  ::ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(table_name, to_table, options)
206
208
  end
207
209
  end
210
+
211
+ def add_index_options(_table_name, _column_name, _options = {})
212
+ index_name, index_type, index_columns, index_options, algorithm, using = super
213
+ algorithm = nil if DatabaseServer.creating_new_shard && algorithm == "CONCURRENTLY"
214
+ [index_name, index_type, index_columns, index_options, algorithm, using]
215
+ end
208
216
  end
209
217
  end
210
218
  end
@@ -1,99 +1,125 @@
1
1
  module Switchman
2
2
  module ActiveRecord
3
3
  module QueryCache
4
- # thread local accessors to replace @query_cache_enabled
5
- def query_cache
6
- thread_cache = Thread.current[:query_cache] ||= {}
7
- thread_cache[self.object_id] ||= Hash.new { |h,sql| h[sql] = {} }
8
- end
4
+ if ::Rails.version < '5.0.1'
5
+ # thread local accessors to replace @query_cache_enabled
6
+ def query_cache
7
+ thread_cache = Thread.current[:query_cache] ||= {}
8
+ thread_cache[self.object_id] ||= Hash.new { |h,sql| h[sql] = {} }
9
+ end
9
10
 
10
- def query_cache_enabled
11
- Thread.current[:query_cache_enabled]
12
- end
11
+ def query_cache_enabled
12
+ Thread.current[:query_cache_enabled]
13
+ end
13
14
 
14
- def query_cache_enabled=(value)
15
- Thread.current[:query_cache_enabled] = value
16
- end
15
+ def query_cache_enabled=(value)
16
+ Thread.current[:query_cache_enabled] = value
17
+ end
17
18
 
18
- # basically wholesale repeat of the methods from the original (see
19
- # https://github.com/rails/rails/blob/master/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb),
20
- # but with self.query_cache_enabled and self.query_cache_enabled= instead
21
- # of @query_cache_enabled.
19
+ # basically wholesale repeat of the methods from the original (see
20
+ # https://github.com/rails/rails/blob/master/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb),
21
+ # but with self.query_cache_enabled and self.query_cache_enabled= instead
22
+ # of @query_cache_enabled.
22
23
 
23
- def enable_query_cache!
24
- self.query_cache_enabled = true
25
- end
24
+ def enable_query_cache!
25
+ self.query_cache_enabled = true
26
+ end
26
27
 
27
- def disable_query_cache!
28
- self.query_cache_enabled = false
29
- end
28
+ def disable_query_cache!
29
+ self.query_cache_enabled = false
30
+ end
30
31
 
31
- def cache
32
- old, self.query_cache_enabled = query_cache_enabled, true
33
- yield
34
- ensure
35
- self.query_cache_enabled = old
36
- clear_query_cache unless self.query_cache_enabled
37
- end
32
+ def cache
33
+ old, self.query_cache_enabled = query_cache_enabled, true
34
+ yield
35
+ ensure
36
+ self.query_cache_enabled = old
37
+ clear_query_cache unless self.query_cache_enabled
38
+ end
38
39
 
39
- def uncached
40
- old, self.query_cache_enabled = query_cache_enabled, false
41
- yield
42
- ensure
43
- self.query_cache_enabled = old
44
- end
40
+ def uncached
41
+ old, self.query_cache_enabled = query_cache_enabled, false
42
+ yield
43
+ ensure
44
+ self.query_cache_enabled = old
45
+ end
45
46
 
46
- def clear_query_cache
47
- Thread.current[:query_cache].try(:clear)
48
- end
47
+ def clear_query_cache
48
+ Thread.current[:query_cache]&.clear
49
+ end
49
50
 
50
- def select_all(arel, name = nil, binds = [], preparable: nil)
51
- if self.query_cache_enabled && !locked?(arel)
52
- arel, binds = binds_from_relation(arel, binds)
53
- sql = to_sql(arel, binds)
54
- if ::Rails.version >= '5'
55
- cache_sql(sql, binds) { super(sql, name, binds, preparable: preparable) }
56
- else
57
- cache_sql(sql, binds) { super(sql, name, binds) }
58
- end
59
- else
60
- if ::Rails.version >= '5'
61
- super
51
+ def select_all(arel, name = nil, binds = [], preparable: nil)
52
+ if self.query_cache_enabled && !locked?(arel)
53
+ arel, binds = binds_from_relation(arel, binds)
54
+ sql = to_sql(arel, binds)
55
+ if ::Rails.version >= '5'
56
+ cache_sql(sql, binds) { super(sql, name, binds, preparable: preparable) }
57
+ else
58
+ cache_sql(sql, binds) { super(sql, name, binds) }
59
+ end
62
60
  else
63
- super(arel, name, binds)
61
+ if ::Rails.version >= '5'
62
+ super
63
+ else
64
+ super(arel, name, binds)
65
+ end
64
66
  end
65
67
  end
66
- end
67
68
 
68
- # no reason to define these on the including class directly. the super
69
- # works just as well from a method on the included module
70
- [:insert, :update, :delete].each do |method_name|
71
- class_eval <<-end_code, __FILE__, __LINE__ + 1
72
- def #{method_name}(*args)
73
- clear_query_cache if self.query_cache_enabled
74
- super
75
- end
76
- end_code
69
+ # no reason to define these on the including class directly. the super
70
+ # works just as well from a method on the included module
71
+ [:insert, :update, :delete].each do |method_name|
72
+ class_eval <<-end_code, __FILE__, __LINE__ + 1
73
+ def #{method_name}(*args)
74
+ clear_query_cache if self.query_cache_enabled
75
+ super
76
+ end
77
+ end_code
78
+ end
77
79
  end
78
80
 
79
81
  private
80
82
 
81
- def cache_sql(sql, binds)
82
- # have to include the shard id in the cache key because of switching dbs on the same connection
83
- sql = "#{self.shard.id}::#{sql}"
84
- result =
85
- if query_cache[sql].key?(binds)
86
- ::ActiveSupport::Notifications.instrument("sql.active_record",
87
- :sql => sql, :binds => binds, :name => "CACHE", :connection_id => object_id)
88
- query_cache[sql][binds]
89
- else
90
- query_cache[sql][binds] = yield
91
- end
83
+ if ::Rails.version < '5.1'
84
+ def cache_sql(sql, binds)
85
+ # have to include the shard id in the cache key because of switching dbs on the same connection
86
+ sql = "#{self.shard.id}::#{sql}"
87
+ result =
88
+ if query_cache[sql].key?(binds)
89
+ ::ActiveSupport::Notifications.instrument("sql.active_record",
90
+ :sql => sql, :binds => binds, :name => "CACHE", :connection_id => object_id)
91
+ query_cache[sql][binds]
92
+ else
93
+ query_cache[sql][binds] = yield
94
+ end
92
95
 
93
- if ::ActiveRecord::Result === result
94
- result.dup
95
- else
96
- result.collect { |row| row.dup }
96
+ if ::ActiveRecord::Result === result
97
+ result.dup
98
+ else
99
+ result.collect { |row| row.dup }
100
+ end
101
+ end
102
+ else
103
+ def cache_sql(sql, name, binds)
104
+ # have to include the shard id in the cache key because of switching dbs on the same connection
105
+ sql = "#{self.shard.id}::#{sql}"
106
+ @lock.synchronize do
107
+ result =
108
+ if query_cache[sql].key?(binds)
109
+ ::ActiveSupport::Notifications.instrument(
110
+ "sql.active_record",
111
+ sql: sql,
112
+ binds: binds,
113
+ name: name,
114
+ connection_id: object_id,
115
+ cached: true,
116
+ )
117
+ query_cache[sql][binds]
118
+ else
119
+ query_cache[sql][binds] = yield
120
+ end
121
+ result.dup
122
+ end
97
123
  end
98
124
  end
99
125
  end
@@ -19,11 +19,11 @@ module Switchman
19
19
  @values[:shard_source]
20
20
  end
21
21
  def shard_value=(value)
22
- raise ImmutableRelation if @loaded
22
+ raise ::ActiveRecord::ImmutableRelation if @loaded
23
23
  @values[:shard] = value
24
24
  end
25
25
  def shard_source_value=(value)
26
- raise ImmutableRelation if @loaded
26
+ raise ::ActiveRecord::ImmutableRelation if @loaded
27
27
  @values[:shard_source] = value
28
28
  end
29
29
 
@@ -126,11 +126,18 @@ module Switchman
126
126
  def transpose_#{type}_clauses(source_shard, target_shard, remove_nonlocal_primary_keys)
127
127
  if ::Rails.version >= '5'
128
128
  unless (predicates = #{type}_clause.send(:predicates)).empty?
129
- new_predicates = transpose_predicates(predicates, source_shard,
130
- target_shard, remove_nonlocal_primary_keys)
131
- if new_predicates != predicates
129
+ new_predicates, new_binds = transpose_predicates(predicates, source_shard,
130
+ target_shard, remove_nonlocal_primary_keys,
131
+ binds: #{type}_clause.binds,
132
+ dup_binds_on_mutation: true)
133
+ if new_predicates != predicates || !new_binds.equal?(#{type}_clause.binds)
132
134
  self.#{type}_clause = #{type}_clause.dup
133
- #{type}_clause.instance_variable_set(:@predicates, new_predicates)
135
+ if new_predicates != predicates
136
+ #{type}_clause.instance_variable_set(:@predicates, new_predicates)
137
+ end
138
+ if !new_binds.equal?(#{type}_clause.binds)
139
+ #{type}_clause.instance_variable_set(:@binds, new_binds)
140
+ end
134
141
  end
135
142
  end
136
143
  else
@@ -149,6 +156,8 @@ module Switchman
149
156
  end
150
157
 
151
158
  def infer_shards_from_primary_key(predicates, binds = nil)
159
+ return unless klass.integral_id?
160
+
152
161
  primary_key = predicates.detect do |predicate|
153
162
  predicate.is_a?(::Arel::Nodes::Binary) && predicate.left.is_a?(::Arel::Attributes::Attribute) &&
154
163
  predicate.left.relation.is_a?(::Arel::Table) && predicate.left.relation.model == klass &&
@@ -181,15 +190,14 @@ module Switchman
181
190
  when ::Arel::Nodes::BindParam
182
191
  # look for a bind param with a matching column name
183
192
  if ::Rails.version >= "5"
184
- binds ||= where_clause.binds + having_clause.binds
185
- if binds && bind = binds.detect{|b| b.try(:name).to_s == klass.primary_key.to_s}
193
+ if binds && bind = binds.detect{|b| b&.name.to_s == klass.primary_key.to_s}
186
194
  unless bind.value.is_a?(::ActiveRecord::StatementCache::Substitute)
187
195
  local_id, id_shard = Shard.local_id_for(bind.value)
188
196
  id_shard ||= Shard.current(klass.shard_category) if local_id
189
197
  end
190
198
  end
191
199
  else
192
- if bind_values && idx = bind_values.find_index{|b| b.is_a?(Array) && b.first.try(:name).to_s == klass.primary_key.to_s}
200
+ if bind_values && idx = bind_values.find_index{|b| b.is_a?(Array) && b.first&.name.to_s == klass.primary_key.to_s}
193
201
  column, value = bind_values[idx]
194
202
  unless value.is_a?(::ActiveRecord::StatementCache::Substitute)
195
203
  local_id, id_shard = Shard.local_id_for(value)
@@ -268,8 +276,13 @@ module Switchman
268
276
 
269
277
  # semi-private
270
278
  public
271
- def transpose_predicates(predicates, source_shard, target_shard, remove_nonlocal_primary_keys = false, binds = nil)
272
- predicates.map do |predicate|
279
+ def transpose_predicates(predicates,
280
+ source_shard,
281
+ target_shard,
282
+ remove_nonlocal_primary_keys = false,
283
+ binds: nil,
284
+ dup_binds_on_mutation: false)
285
+ result = predicates.map do |predicate|
273
286
  next predicate unless predicate.is_a?(::Arel::Nodes::Binary)
274
287
  next predicate unless predicate.left.is_a?(::Arel::Attributes::Attribute)
275
288
  relation, column = relation_and_column(predicate.left)
@@ -312,8 +325,13 @@ module Switchman
312
325
  when ::Arel::Nodes::BindParam
313
326
  # look for a bind param with a matching column name
314
327
  if ::Rails.version >= "5"
315
- binds ||= where_clause.binds + having_clause.binds
316
- if binds && bind = binds.detect{|b| b.try(:name).to_s == predicate.left.name.to_s}
328
+ if binds && bind = binds.detect{|b| b&.name.to_s == predicate.left.name.to_s}
329
+ # before we mutate, dup
330
+ if dup_binds_on_mutation
331
+ binds = binds.map(&:dup)
332
+ dup_binds_on_mutation = false
333
+ bind = binds.find { |b| b&.name.to_s == predicate.left.name.to_s }
334
+ end
317
335
  if bind.value.is_a?(::ActiveRecord::StatementCache::Substitute)
318
336
  bind.value.sharded = true # mark for transposition later
319
337
  bind.value.primary = true if type == :primary
@@ -325,7 +343,7 @@ module Switchman
325
343
  end
326
344
  end
327
345
  else
328
- if bind_values && idx = bind_values.find_index{|b| b.is_a?(Array) && b.first.try(:name).to_s == predicate.left.name.to_s}
346
+ if bind_values && idx = bind_values.find_index{|b| b.is_a?(Array) && b.first&.name.to_s == predicate.left.name.to_s}
329
347
  column, value = bind_values[idx]
330
348
  if value.is_a?(::ActiveRecord::StatementCache::Substitute)
331
349
  value.sharded = true # mark for transposition later
@@ -356,6 +374,8 @@ module Switchman
356
374
  predicate.class.new(predicate.left, new_right_value)
357
375
  end
358
376
  end
377
+ result = [result, binds] if ::Rails.version >= '5'
378
+ result
359
379
  end
360
380
  end
361
381
  end
@@ -44,12 +44,22 @@ module Switchman
44
44
  end
45
45
  end
46
46
 
47
- def generic_query_builder(connection)
48
- @query_builder ||= connection.cacheable_query(@arel)
49
- end
47
+ if ::Rails.version < '5.1'
48
+ def generic_query_builder(connection)
49
+ @query_builder ||= connection.cacheable_query(@arel)
50
+ end
51
+
52
+ def qualified_query_builder(shard, klass)
53
+ @qualified_query_builders[shard.id] ||= klass.connection.cacheable_query(@arel)
54
+ end
55
+ else
56
+ def generic_query_builder(connection)
57
+ @query_builder ||= connection.cacheable_query(self.class, @arel)
58
+ end
50
59
 
51
- def qualified_query_builder(shard, klass)
52
- @qualified_query_builders[shard.id] ||= klass.connection.cacheable_query(@arel)
60
+ def qualified_query_builder(shard, klass)
61
+ @qualified_query_builders[shard.id] ||= klass.connection.cacheable_query(self.class, @arel)
62
+ end
53
63
  end
54
64
 
55
65
  module BindMap
@@ -21,7 +21,7 @@ module Switchman
21
21
  where_clause = super
22
22
  predicates = where_clause.send(:predicates)
23
23
  @scope.send(:infer_shards_from_primary_key, predicates, where_clause.binds) if @scope.shard_source_value == :implicit && @scope.shard_value.is_a?(Shard)
24
- predicates = @scope.transpose_predicates(predicates, nil, @scope.primary_shard, false, where_clause.binds)
24
+ predicates, _new_binds = @scope.transpose_predicates(predicates, nil, @scope.primary_shard, false, binds: where_clause.binds)
25
25
  where_clause.instance_variable_set(:@predicates, predicates)
26
26
  where_clause
27
27
  else
@@ -1,14 +1,13 @@
1
1
  module Switchman
2
2
  module CallSuper
3
3
  def super_method_above(method_name, above_module)
4
- @super_methods ||= {}
5
- @super_methods[[method_name, above_module]] ||= begin
6
- method = method(method_name)
7
- while method.owner != above_module
8
- method = method.super_method
9
- end
10
- method.super_method
4
+ method = method(method_name)
5
+ last_owner = method.owner
6
+ while method.owner != above_module
7
+ method = method.super_method
8
+ raise "Could not find super method ``#{method_name}' for #{self.class}" if method.owner == last_owner
11
9
  end
10
+ method.super_method
12
11
  end
13
12
 
14
13
  def call_super(method, above_module, *args, &block)
@@ -10,7 +10,7 @@ module Switchman
10
10
  end
11
11
 
12
12
  class ConnectionPoolProxy
13
- delegate :spec, :connected?, :default_schema, :with_connection,
13
+ delegate :spec, :connected?, :default_schema, :with_connection, :query_cache_enabled, :active_connection?,
14
14
  :to => :current_pool
15
15
 
16
16
  attr_reader :category, :schema_cache
@@ -71,7 +71,12 @@ module Switchman
71
71
  end
72
72
  end
73
73
 
74
- %w{release_connection disconnect! clear_reloadable_connections! verify_active_connections! clear_stale_cached_connections!}.each do |method|
74
+ %w{release_connection disconnect!
75
+ clear_reloadable_connections!
76
+ verify_active_connections!
77
+ clear_stale_cached_connections!
78
+ enable_query_cache!
79
+ disable_query_cache! }.each do |method|
75
80
  class_eval(<<-EOS)
76
81
  def #{method}
77
82
  @connection_pools.values.each(&:#{method})
@@ -126,6 +131,9 @@ module Switchman
126
131
 
127
132
  ::ActiveRecord::ConnectionAdapters::ConnectionPool.new(spec).tap do |pool|
128
133
  pool.shard = shard
134
+ if ::Rails.version >= '5.0.1'
135
+ pool.enable_query_cache! if !@connection_pools.empty? && @connection_pools.first.last.query_cache_enabled
136
+ end
129
137
  end
130
138
  end
131
139
  end
@@ -3,6 +3,8 @@ module Switchman
3
3
  attr_accessor :id
4
4
 
5
5
  class << self
6
+ attr_accessor :creating_new_shard
7
+
6
8
  def all
7
9
  database_servers.values
8
10
  end
@@ -134,7 +136,7 @@ module Switchman
134
136
  create_schema = options[:schema]
135
137
  # look for another shard associated with this db
136
138
  other_shard = self.shards.where("name<>':memory:' OR name IS NULL").order(:id).first
137
- temp_name = other_shard.try(:name) unless id == ::Rails.env
139
+ temp_name = other_shard&.name unless id == ::Rails.env
138
140
  temp_name = Shard.default.name if id == ::Rails.env
139
141
 
140
142
  case config[:adapter]
@@ -158,7 +160,7 @@ module Switchman
158
160
  create_statement = lambda { "CREATE DATABASE #{name}" }
159
161
  end
160
162
  sharding_config = Switchman.config
161
- config_create_statement = sharding_config[config[:adapter]].try(:[], :create_statement)
163
+ config_create_statement = sharding_config[config[:adapter]]&.[](:create_statement)
162
164
  config_create_statement ||= sharding_config[:create_statement]
163
165
  if config_create_statement
164
166
  create_commands = Array(config_create_statement).dup
@@ -173,6 +175,7 @@ module Switchman
173
175
  shard.id = options[:id] if options[:id]
174
176
  end
175
177
  begin
178
+ self.class.creating_new_shard = true
176
179
  if name.nil?
177
180
  base_name = self.config[:database] % self.config
178
181
  base_name = $1 if base_name =~ /(?:.*\/)(.+)_shard_\d+(?:\.sqlite3)?$/
@@ -233,6 +236,8 @@ module Switchman
233
236
  shard.drop_database if shard.name == name rescue nil
234
237
  reset_column_information unless create_schema == false rescue nil
235
238
  raise
239
+ ensure
240
+ self.class.creating_new_shard = false
236
241
  end
237
242
  end
238
243
 
@@ -2,7 +2,7 @@ module Switchman
2
2
  class Engine < ::Rails::Engine
3
3
  isolate_namespace Switchman
4
4
 
5
- config.autoload_once_paths << File.expand_path(File.join(__FILE__, "../../../app/models"))
5
+ config.autoload_once_paths << File.expand_path("app/models", config.paths.path)
6
6
 
7
7
  def self.lookup_stores(cache_store_config)
8
8
  result = {}
@@ -72,6 +72,7 @@ module Switchman
72
72
  require "switchman/active_record/connection_pool"
73
73
  require "switchman/active_record/finder_methods"
74
74
  require "switchman/active_record/log_subscriber"
75
+ require "switchman/active_record/migration"
75
76
  require "switchman/active_record/model_schema"
76
77
  require "switchman/active_record/persistence"
77
78
  require "switchman/active_record/predicate_builder"
@@ -87,7 +88,7 @@ module Switchman
87
88
  require "switchman/call_super"
88
89
  require "switchman/rails"
89
90
  require "switchman/shackles/relation"
90
- require "switchman/shard_internal"
91
+ require_dependency "switchman/shard_internal"
91
92
  require "switchman/standard_error"
92
93
 
93
94
  ::StandardError.include(StandardError)
@@ -124,9 +125,15 @@ module Switchman
124
125
  # we want it to find the definition from
125
126
  # ActiveRecord::ConnectionAdapters::DatabaseStatements, not
126
127
  # ActiveRecord::ConnectionAdapters::QueryCache
127
- ::ActiveRecord::ConnectionAdapters::QueryCache.send(:remove_method, :select_all)
128
+ ::ActiveRecord::ConnectionAdapters::QueryCache.send(:remove_method, :select_all) if ::Rails.version < '5.0.1'
128
129
 
129
130
  ::ActiveRecord::LogSubscriber.prepend(ActiveRecord::LogSubscriber)
131
+ ::ActiveRecord::Migration.prepend(ActiveRecord::Migration)
132
+ if ::Rails.version >= '5'
133
+ ::ActiveRecord::Migration::Compatibility::V5_0.prepend(ActiveRecord::Migration::Compatibility::V5_0)
134
+ end
135
+ ::ActiveRecord::Migrator.prepend(ActiveRecord::Migrator)
136
+
130
137
  ::ActiveRecord::Reflection::AbstractReflection.include(ActiveRecord::Reflection::AbstractReflection)
131
138
  ::ActiveRecord::Reflection::AssociationReflection.prepend(ActiveRecord::Reflection::AssociationScopeCache)
132
139
  ::ActiveRecord::Reflection::ThroughReflection.prepend(ActiveRecord::Reflection::AssociationScopeCache)
@@ -118,7 +118,8 @@ module Switchman
118
118
  klass.before do
119
119
  raise "Sharding did not set up correctly" if @@sharding_failed
120
120
  Shard.clear_cache
121
- if use_transactional_fixtures
121
+ if ::Rails.version >= '5.1' ? use_transactional_tests :
122
+ (::Rails.version >= '5' && use_transactional_tests) || use_transactional_fixtures
122
123
  Shard.default(true)
123
124
  @shard1 = Shard.find(@shard1.id)
124
125
  @shard2 = Shard.find(@shard2.id)
@@ -134,7 +135,8 @@ module Switchman
134
135
 
135
136
  klass.after do
136
137
  next if @@sharding_failed
137
- if use_transactional_fixtures
138
+ if ::Rails.version >= '5.1' ? use_transactional_tests :
139
+ (::Rails.version >= '5' && use_transactional_tests) || use_transactional_fixtures
138
140
  shards = [@shard2]
139
141
  shards << @shard1 unless @shard1.database_server == Shard.default.database_server
140
142
  shards.each do |shard|
@@ -6,7 +6,7 @@ module Switchman
6
6
  end
7
7
 
8
8
  def instrument(name, payload={})
9
- shard = @shard_host.try(:shard)
9
+ shard = @shard_host&.shard
10
10
  # attribute_methods_generated? will be false during a reload -
11
11
  # when we might be doing a query while defining attribute methods,
12
12
  # so just avoid logging then
@@ -1,3 +1,3 @@
1
1
  module Switchman
2
- VERSION = "1.9.0"
2
+ VERSION = "1.10.0"
3
3
  end
@@ -60,21 +60,23 @@ module Switchman
60
60
  end
61
61
 
62
62
  ::Shackles.activate(:deploy) do
63
- begin
64
- categories = categories.call if categories.respond_to?(:call)
65
- Shard.with_each_shard(scope, categories, options) do
66
- shard = Shard.current
67
- puts "#{shard.id}: #{shard.description}"
68
- ::ActiveRecord::Base.connection_pool.spec.config[:shard_name] = Shard.current.name
69
- ::ActiveRecord::Base.configurations[::Rails.env] = ::ActiveRecord::Base.connection_pool.spec.config.stringify_keys
70
- shard.database_server.unshackle do
71
- old_actions.each { |action| action.call(*task_args) }
63
+ Shard.default.database_server.unshackle do
64
+ begin
65
+ categories = categories.call if categories.respond_to?(:call)
66
+ Shard.with_each_shard(scope, categories, options) do
67
+ shard = Shard.current
68
+ puts "#{shard.id}: #{shard.description}"
69
+ ::ActiveRecord::Base.connection_pool.spec.config[:shard_name] = Shard.current.name
70
+ ::ActiveRecord::Base.configurations[::Rails.env] = ::ActiveRecord::Base.connection_pool.spec.config.stringify_keys
71
+ shard.database_server.unshackle do
72
+ old_actions.each { |action| action.call(*task_args) }
73
+ end
74
+ nil
72
75
  end
73
- nil
76
+ rescue => e
77
+ puts "Exception from #{e.current_shard.id}: #{e.current_shard.description}" if options[:parallel] != 0
78
+ raise
74
79
  end
75
- rescue => e
76
- puts "Exception from #{e.current_shard.id}: #{e.current_shard.description}" if options[:parallel] != 0
77
- raise
78
80
  end
79
81
  end
80
82
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: switchman
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.0
4
+ version: 1.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cody Cutrer
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2016-12-08 00:00:00.000000000 Z
13
+ date: 2017-06-15 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: railties
@@ -19,9 +19,9 @@ dependencies:
19
19
  - - ">="
20
20
  - !ruby/object:Gem::Version
21
21
  version: '4.2'
22
- - - "<="
22
+ - - "<"
23
23
  - !ruby/object:Gem::Version
24
- version: '5.1'
24
+ version: '5.2'
25
25
  type: :runtime
26
26
  prerelease: false
27
27
  version_requirements: !ruby/object:Gem::Requirement
@@ -29,9 +29,9 @@ dependencies:
29
29
  - - ">="
30
30
  - !ruby/object:Gem::Version
31
31
  version: '4.2'
32
- - - "<="
32
+ - - "<"
33
33
  - !ruby/object:Gem::Version
34
- version: '5.1'
34
+ version: '5.2'
35
35
  - !ruby/object:Gem::Dependency
36
36
  name: activerecord
37
37
  requirement: !ruby/object:Gem::Requirement
@@ -39,9 +39,9 @@ dependencies:
39
39
  - - ">="
40
40
  - !ruby/object:Gem::Version
41
41
  version: '4.2'
42
- - - "<="
42
+ - - "<"
43
43
  - !ruby/object:Gem::Version
44
- version: '5.1'
44
+ version: '5.2'
45
45
  type: :runtime
46
46
  prerelease: false
47
47
  version_requirements: !ruby/object:Gem::Requirement
@@ -49,23 +49,23 @@ dependencies:
49
49
  - - ">="
50
50
  - !ruby/object:Gem::Version
51
51
  version: '4.2'
52
- - - "<="
52
+ - - "<"
53
53
  - !ruby/object:Gem::Version
54
- version: '5.1'
54
+ version: '5.2'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: shackles
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: 1.3.0
61
+ version: '1.3'
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: 1.3.0
68
+ version: '1.3'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: open4
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -86,14 +86,14 @@ dependencies:
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: 2.1.0
89
+ version: '2.1'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: 2.1.0
96
+ version: '2.1'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: byebug
99
99
  requirement: !ruby/object:Gem::Requirement
@@ -108,20 +108,6 @@ dependencies:
108
108
  - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
- - !ruby/object:Gem::Dependency
112
- name: mysql2
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - "~>"
116
- - !ruby/object:Gem::Version
117
- version: '0.3'
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - "~>"
123
- - !ruby/object:Gem::Version
124
- version: '0.3'
125
111
  - !ruby/object:Gem::Dependency
126
112
  name: pg
127
113
  requirement: !ruby/object:Gem::Requirement
@@ -142,14 +128,14 @@ dependencies:
142
128
  requirements:
143
129
  - - '='
144
130
  - !ruby/object:Gem::Version
145
- version: 3.1.0
131
+ version: 3.5.2
146
132
  type: :development
147
133
  prerelease: false
148
134
  version_requirements: !ruby/object:Gem::Requirement
149
135
  requirements:
150
136
  - - '='
151
137
  - !ruby/object:Gem::Version
152
- version: 3.1.0
138
+ version: 3.5.2
153
139
  - !ruby/object:Gem::Dependency
154
140
  name: sqlite3
155
141
  requirement: !ruby/object:Gem::Requirement
@@ -170,14 +156,14 @@ dependencies:
170
156
  requirements:
171
157
  - - "~>"
172
158
  - !ruby/object:Gem::Version
173
- version: '11.3'
159
+ version: '12.0'
174
160
  type: :development
175
161
  prerelease: false
176
162
  version_requirements: !ruby/object:Gem::Requirement
177
163
  requirements:
178
164
  - - "~>"
179
165
  - !ruby/object:Gem::Version
180
- version: '11.3'
166
+ version: '12.0'
181
167
  description: Sharding
182
168
  email:
183
169
  - cody@instructure.com
@@ -203,6 +189,7 @@ files:
203
189
  - lib/switchman/active_record/connection_pool.rb
204
190
  - lib/switchman/active_record/finder_methods.rb
205
191
  - lib/switchman/active_record/log_subscriber.rb
192
+ - lib/switchman/active_record/migration.rb
206
193
  - lib/switchman/active_record/model_schema.rb
207
194
  - lib/switchman/active_record/persistence.rb
208
195
  - lib/switchman/active_record/postgresql_adapter.rb
@@ -256,7 +243,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
256
243
  version: '0'
257
244
  requirements: []
258
245
  rubyforge_project:
259
- rubygems_version: 2.5.2
246
+ rubygems_version: 2.6.11
260
247
  signing_key:
261
248
  specification_version: 4
262
249
  summary: Rails 4 sharding magic