switchman 1.6.1 → 2.0.9

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 (56) hide show
  1. checksums.yaml +5 -5
  2. data/app/models/switchman/shard.rb +746 -11
  3. data/db/migrate/20130328212039_create_switchman_shards.rb +3 -1
  4. data/db/migrate/20130328224244_create_default_shard.rb +4 -2
  5. data/db/migrate/20161206323434_add_back_default_string_limits_switchman.rb +13 -0
  6. data/db/migrate/20180828183945_add_default_shard_index.rb +15 -0
  7. data/db/migrate/20180828192111_add_timestamps_to_shards.rb +17 -0
  8. data/db/migrate/20190114212900_add_unique_name_indexes.rb +9 -0
  9. data/lib/switchman/action_controller/caching.rb +2 -0
  10. data/lib/switchman/active_record/abstract_adapter.rb +14 -4
  11. data/lib/switchman/active_record/association.rb +64 -37
  12. data/lib/switchman/active_record/attribute_methods.rb +16 -5
  13. data/lib/switchman/active_record/base.rb +67 -22
  14. data/lib/switchman/active_record/batches.rb +3 -1
  15. data/lib/switchman/active_record/calculations.rb +16 -21
  16. data/lib/switchman/active_record/connection_handler.rb +71 -79
  17. data/lib/switchman/active_record/connection_pool.rb +28 -23
  18. data/lib/switchman/active_record/finder_methods.rb +20 -29
  19. data/lib/switchman/active_record/log_subscriber.rb +14 -19
  20. data/lib/switchman/active_record/migration.rb +80 -0
  21. data/lib/switchman/active_record/model_schema.rb +3 -1
  22. data/lib/switchman/active_record/persistence.rb +9 -1
  23. data/lib/switchman/active_record/postgresql_adapter.rb +166 -126
  24. data/lib/switchman/active_record/predicate_builder.rb +2 -0
  25. data/lib/switchman/active_record/query_cache.rb +22 -87
  26. data/lib/switchman/active_record/query_methods.rb +122 -126
  27. data/lib/switchman/active_record/reflection.rb +37 -20
  28. data/lib/switchman/active_record/relation.rb +50 -29
  29. data/lib/switchman/active_record/spawn_methods.rb +2 -0
  30. data/lib/switchman/active_record/statement_cache.rb +44 -52
  31. data/lib/switchman/active_record/table_definition.rb +4 -2
  32. data/lib/switchman/active_record/type_caster.rb +2 -0
  33. data/lib/switchman/active_record/where_clause_factory.rb +5 -2
  34. data/lib/switchman/active_support/cache.rb +18 -0
  35. data/lib/switchman/arel.rb +8 -25
  36. data/lib/switchman/call_super.rb +19 -0
  37. data/lib/switchman/connection_pool_proxy.rb +70 -24
  38. data/lib/switchman/database_server.rb +69 -59
  39. data/lib/switchman/default_shard.rb +3 -0
  40. data/lib/switchman/engine.rb +42 -40
  41. data/lib/switchman/environment.rb +2 -0
  42. data/lib/switchman/errors.rb +2 -0
  43. data/lib/switchman/{shackles → guard_rail}/relation.rb +7 -5
  44. data/lib/switchman/{shackles.rb → guard_rail.rb} +6 -4
  45. data/lib/switchman/open4.rb +2 -0
  46. data/lib/switchman/r_spec_helper.rb +14 -8
  47. data/lib/switchman/rails.rb +2 -0
  48. data/lib/switchman/schema_cache.rb +17 -0
  49. data/lib/switchman/sharded_instrumenter.rb +4 -2
  50. data/lib/switchman/standard_error.rb +4 -2
  51. data/lib/switchman/test_helper.rb +6 -3
  52. data/lib/switchman/version.rb +3 -1
  53. data/lib/switchman.rb +3 -1
  54. data/lib/tasks/switchman.rake +46 -72
  55. metadata +87 -41
  56. data/app/models/switchman/shard_internal.rb +0 -692
@@ -1,29 +1,28 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'switchman/connection_pool_proxy'
2
4
 
3
5
  module Switchman
4
6
  module ActiveRecord
5
7
  module ConnectionHandler
6
- def self.make_sharing_automagic(config)
7
- key = config[:adapter] == 'postgresql' ? :schema_search_path : :database
8
-
8
+ def self.make_sharing_automagic(config, shard = Shard.current)
9
9
  # only load the shard name from the db if we have to
10
- if config[key] || !config[:shard_name]
10
+ if !config[:shard_name]
11
11
  # we may not be able to connect to this shard yet, cause it might be an empty database server
12
- shard_name = Shard.current.name rescue nil
12
+ shard = shard.call if shard.is_a?(Proc)
13
+ shard_name = shard.name rescue nil
13
14
  return unless shard_name
14
15
 
15
16
  config[:shard_name] ||= shard_name
16
17
  end
17
-
18
- if !config[key] || config[key] == shard_name
19
- # this may truncate the schema_search_path if it was not specified in database.yml
20
- # but that's what our old behavior was anyway, so I guess it's okay
21
- config[key] = '%{shard_name}'
22
- end
23
18
  end
24
19
 
25
- def establish_connection(owner, spec)
26
- super
20
+ def establish_connection(spec)
21
+ # Just skip establishing a sharded connection if sharding isn't loaded; we'll do it again later
22
+ # This only can happen when loading ActiveRecord::Base; after everything is loaded Shard will
23
+ # be defined and this will actually establish a connection
24
+ return unless defined?(Shard)
25
+ pool = super
27
26
 
28
27
  # this is the first place that the adapter would have been required; but now we
29
28
  # need this addition ASAP since it will be called when loading the default shard below
@@ -32,45 +31,48 @@ module Switchman
32
31
  ::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(ActiveRecord::PostgreSQLAdapter)
33
32
  end
34
33
 
35
- # AR3 uses the name, AR4 uses the model
36
- model = case owner
37
- when String
38
- owner.constantize
39
- when Class
40
- owner
41
- else
42
- raise "unknown owner #{owner}"
43
- end
44
- pool = owner_to_pool[owner.name]
45
-
46
34
  first_time = !Shard.instance_variable_get(:@default)
47
35
  if first_time
48
36
  # Have to cache the default shard before we insert sharding, otherwise the first access
49
37
  # to sharding will recurse onto itself trying to access column information
50
38
  Shard.default
51
39
 
40
+ config = pool.spec.config
52
41
  # automatically change config to allow for sharing connections with simple config
53
- ConnectionHandler.make_sharing_automagic(spec.config)
42
+ ConnectionHandler.make_sharing_automagic(config)
54
43
  ConnectionHandler.make_sharing_automagic(Shard.default.database_server.config)
55
44
 
56
- ::ActiveRecord::Base.configurations[::Rails.env] = spec.instance_variable_get(:@config).stringify_keys
45
+ if ::Rails.version < '6.0'
46
+ ::ActiveRecord::Base.configurations[::Rails.env] = config.stringify_keys
47
+ else
48
+ # Adopted from the deprecated code that currently lives in rails proper
49
+ remaining_configs = ::ActiveRecord::Base.configurations.configurations.reject { |db_config| db_config.env_name == ::Rails.env }
50
+ new_config = ::ActiveRecord::DatabaseConfigurations.new(::Rails.env => config.stringify_keys).configurations
51
+ new_configs = remaining_configs + new_config
52
+
53
+ ::ActiveRecord::Base.configurations = new_configs
54
+ end
55
+ else
56
+ # this is probably wrong now
57
+ Shard.default.remove_instance_variable(:@name) if Shard.default.instance_variable_defined?(:@name)
57
58
  end
58
- @shard_connection_pools ||= { [:master, Shard.default.database_server.shareable? ? ::Rails.env : Shard.default] => pool}
59
59
 
60
- proxy = ConnectionPoolProxy.new(model.shard_category,
60
+ @shard_connection_pools ||= { [:primary, Shard.default.database_server.shareable? ? ::Rails.env : Shard.default] => pool}
61
+
62
+ category = pool.spec.name.to_sym
63
+ proxy = ConnectionPoolProxy.new(category,
61
64
  pool,
62
65
  @shard_connection_pools)
63
- owner_to_pool[owner.name] = proxy
64
- class_to_pool.clear
66
+ owner_to_pool[pool.spec.name] = proxy
65
67
 
66
68
  if first_time
67
- if Shard.default.database_server.config[:prefer_slave]
68
- Shard.default.database_server.shackle!
69
+ if Shard.default.database_server.config[:prefer_secondary]
70
+ Shard.default.database_server.guard!
69
71
  end
70
72
 
71
- if Shard.default.is_a?(DefaultShard) && Shard.default.database_server.config[:slave]
72
- Shard.default.database_server.shackle!
73
- Shard.default(true)
73
+ if Shard.default.is_a?(DefaultShard) && Shard.default.database_server.config[:secondary]
74
+ Shard.default.database_server.guard!
75
+ Shard.default(reload: true)
74
76
  end
75
77
  end
76
78
 
@@ -79,7 +81,7 @@ module Switchman
79
81
  # DON'T do it if we're not the current connection handler - that means
80
82
  # we're in the middle of switching environments, and we don't want to
81
83
  # establish a connection with incorrect settings
82
- if (model == ::ActiveRecord::Base || model == Shard) && self == ::ActiveRecord::Base.connection_handler && !first_time
84
+ if [:primary, :unsharded].include?(category) && self == ::ActiveRecord::Base.connection_handler && !first_time
83
85
  Shard.default(reload: true, with_fallback: true)
84
86
  proxy.disconnect!
85
87
  end
@@ -89,12 +91,15 @@ module Switchman
89
91
  if Shard.default.is_a?(Shard)
90
92
  DatabaseServer.all.each do |server|
91
93
  next if server == Shard.default.database_server
92
- shard = server.shards.where(:name => nil).first
93
- shard ||= Shard.new(:database_server => server)
94
- shard.activate do
95
- ConnectionHandler.make_sharing_automagic(server.config)
96
- ConnectionHandler.make_sharing_automagic(proxy.current_pool.spec.config)
94
+
95
+ shard = nil
96
+ shard_proc = -> do
97
+ shard ||= server.shards.where(:name => nil).first
98
+ shard ||= Shard.new(:database_server => server)
99
+ shard
97
100
  end
101
+ ConnectionHandler.make_sharing_automagic(server.config, shard_proc)
102
+ ConnectionHandler.make_sharing_automagic(proxy.current_pool.spec.config, shard_proc)
98
103
  end
99
104
  end
100
105
  # we may have established some connections above trying to infer the shard's name.
@@ -106,60 +111,47 @@ module Switchman
106
111
  proxy
107
112
  end
108
113
 
109
- def remove_connection(model)
110
- uninitialize_ar(model) if owner_to_pool[model.name].is_a?(ConnectionPoolProxy)
111
- result = super
112
- initialize_categories
113
- result
114
+ def remove_connection(spec_name)
115
+ pool = owner_to_pool[spec_name]
116
+ owner_to_pool[spec_name] = pool.default_pool if pool.is_a?(ConnectionPoolProxy)
117
+ super
114
118
  end
115
119
 
116
- def pool_for(owner)
117
- # copypasted from AR#ConnectionHandler other than proxy handling
118
-
119
- owner_to_pool.fetch(owner.name) {
120
- if ancestor_pool = pool_from_any_process_for(owner)
120
+ def retrieve_connection_pool(spec_name)
121
+ owner_to_pool.fetch(spec_name) do
122
+ if ancestor_pool = pool_from_any_process_for(spec_name)
121
123
  # A connection was established in an ancestor process that must have
122
124
  # subsequently forked. We can't reuse the connection, but we can copy
123
125
  # the specification and establish a new connection with it.
124
- if ancestor_pool.is_a?(ConnectionPoolProxy)
125
- establish_connection owner, ancestor_pool.default_pool.spec
126
+ spec = if ancestor_pool.is_a?(ConnectionPoolProxy)
127
+ ancestor_pool.default_pool.spec
126
128
  else
127
- establish_connection owner, ancestor_pool.spec
129
+ ancestor_pool.spec
130
+ end
131
+ pool = establish_connection(spec.to_hash)
132
+ pool.instance_variable_set(:@schema_cache, ancestor_pool.schema_cache) if ancestor_pool.schema_cache
133
+ pool
134
+ elsif spec_name != "primary"
135
+ primary_pool = retrieve_connection_pool("primary")
136
+ if primary_pool.is_a?(ConnectionPoolProxy)
137
+ pool = ConnectionPoolProxy.new(spec_name.to_sym, primary_pool.default_pool, @shard_connection_pools)
138
+ pool.schema_cache.copy_references(primary_pool.schema_cache)
139
+ pool
140
+ else
141
+ primary_pool
128
142
  end
129
143
  else
130
- owner_to_pool[owner.name] = nil
144
+ owner_to_pool[spec_name] = nil
131
145
  end
132
- }
133
- end
134
-
135
- def retrieve_connection_pool(klass)
136
- class_to_pool[klass.name] ||= begin
137
- original_klass = klass
138
- until pool = pool_for(klass)
139
- klass = klass.superclass
140
- break unless klass <= Base
141
- end
142
-
143
- if pool.is_a?(ConnectionPoolProxy) && pool.category != original_klass.shard_category
144
- default_pool = pool.default_pool
145
- pool = nil
146
- class_to_pool.each_value { |p| pool = p if p.is_a?(ConnectionPoolProxy) &&
147
- p.category == original_klass.shard_category &&
148
- p.default_pool == default_pool }
149
- pool ||= ConnectionPoolProxy.new(original_klass.shard_category, default_pool, @shard_connection_pools)
150
- end
151
-
152
- class_to_pool[original_klass.name] = pool
153
146
  end
154
147
  end
155
148
 
156
149
  def clear_idle_connections!(since_when)
157
- # TODO in rails 4.2+ s/connection_pools.values/connection_pool_list/
158
- connection_pools.values.each{ |pool| pool.clear_idle_connections!(since_when) }
150
+ connection_pool_list.each{ |pool| pool.clear_idle_connections!(since_when) }
159
151
  end
160
152
 
161
153
  def switchman_connection_pool_proxies
162
- class_to_pool.values.uniq
154
+ owner_to_pool.values.uniq.select{|p| p.is_a?(ConnectionPoolProxy)}
163
155
  end
164
156
 
165
157
  private
@@ -1,14 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'switchman/errors'
2
4
 
3
5
  module Switchman
4
6
  module ActiveRecord
5
7
  module ConnectionPool
6
8
  def shard
7
- Thread.current["#{object_id}_shard".to_sym] || Shard.default
9
+ Thread.current[tls_key] || Shard.default
8
10
  end
9
11
 
10
12
  def shard=(value)
11
- Thread.current["#{object_id}_shard".to_sym] = value
13
+ Thread.current[tls_key] = value
12
14
  end
13
15
 
14
16
  def default_schema
@@ -32,19 +34,14 @@ module Switchman
32
34
  conn
33
35
  end
34
36
 
35
- def connection
36
- conn = super
37
+ def connection(switch_shard: true)
38
+ conn = super()
37
39
  raise NonExistentShardError if shard.new_record?
38
- switch_database(conn) if conn.shard != self.shard
40
+ switch_database(conn) if conn.shard != self.shard && switch_shard
39
41
  conn
40
42
  end
41
43
 
42
- def release_connection(with_id = nil)
43
- with_id ||= if ::Rails.version >= '5'
44
- Thread.current
45
- else
46
- current_connection_id
47
- end
44
+ def release_connection(with_id = Thread.current)
48
45
  super(with_id)
49
46
 
50
47
  if spec.config[:idle_timeout]
@@ -52,6 +49,20 @@ module Switchman
52
49
  end
53
50
  end
54
51
 
52
+ def remove_shard!(shard)
53
+ synchronize do
54
+ # The shard might be currently active, so we need to update our own shard
55
+ if self.shard == shard
56
+ self.shard = Shard.default
57
+ end
58
+ # Update out any connections that may be using this shard
59
+ @connections.each do |conn|
60
+ # This will also update the connection's shard to the default shard
61
+ switch_database(conn) if conn.shard == shard
62
+ end
63
+ end
64
+ end
65
+
55
66
  def clear_idle_connections!(since_when)
56
67
  synchronize do
57
68
  @connections.reject! do |conn|
@@ -75,20 +86,14 @@ module Switchman
75
86
  end
76
87
 
77
88
  spec.config[:shard_name] = self.shard.name
78
- case conn.adapter_name
79
- when 'MySQL', 'Mysql2'
80
- conn.execute("USE #{spec.config[:database]}")
81
- when 'PostgreSQL'
82
- if conn.schema_search_path != spec.config[:schema_search_path]
83
- conn.schema_search_path = spec.config[:schema_search_path]
84
- end
85
- when 'SQLite'
86
- # This is an artifact of the adapter modifying the path to be an absolute path when it is instantiated; just let it slide
87
- else
88
- raise("Cannot switch databases on same DatabaseServer with adapter type: #{conn.adapter_name}. Limit one Shard per DatabaseServer.")
89
- end
90
89
  conn.shard = shard
91
90
  end
91
+
92
+ private
93
+
94
+ def tls_key
95
+ "#{object_id}_shard".to_sym
96
+ end
92
97
  end
93
98
  end
94
99
  end
@@ -1,9 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Switchman
2
4
  module ActiveRecord
3
5
  module FinderMethods
4
- def find_one(id, call_super: false)
6
+ def find_one(id)
5
7
  return super(id) unless klass.integral_id?
6
- return super(id) if call_super
7
8
 
8
9
  if shard_source_value != :implicit
9
10
  current_shard = Shard.current(klass.shard_category)
@@ -14,7 +15,7 @@ module Switchman
14
15
  # skip the shard if the object can't be on it. unless we're only looking at one shard;
15
16
  # we might be expecting a shadow object
16
17
  next if current_id > Shard::IDS_PER_SHARD && self.all_shards.length > 1
17
- relation.send(:find_one, current_id, call_super: true)
18
+ relation.call_super(:find_one, FinderMethods, current_id)
18
19
  end
19
20
  if result.is_a?(Array)
20
21
  result = result.first
@@ -26,24 +27,18 @@ module Switchman
26
27
 
27
28
  local_id, shard = Shard.local_id_for(id)
28
29
  if shard
29
- if ::Rails.version < '4.2'
30
- # find_one uses binds, so we can't depend on QueryMethods
31
- # catching it
32
- begin
33
- old_shard_value = shard_value
34
- self.shard_value = shard
35
- super(local_id)
36
- ensure
37
- self.shard_value = old_shard_value
38
- end
39
- else
40
- shard.activate { super(local_id) }
41
- end
30
+ shard.activate { super(local_id) }
42
31
  else
43
32
  super(id)
44
33
  end
45
34
  end
46
35
 
36
+ def find_some_ordered(ids)
37
+ current_shard = Shard.current(klass.shard_category)
38
+ ids = ids.map{|id| Shard.relative_id_for(id, current_shard, current_shard)}
39
+ super(ids)
40
+ end
41
+
47
42
  def find_or_instantiator_by_attributes(match, attributes, *args)
48
43
  primary_shard.activate { super }
49
44
  end
@@ -52,13 +47,10 @@ module Switchman
52
47
  conditions = conditions.id if ::ActiveRecord::Base === conditions
53
48
  return false if !conditions
54
49
 
55
- if ::Rails.version >= '4.1'
56
- relation = apply_join_dependency(self, construct_join_dependency)
57
- return false if ::ActiveRecord::NullRelation === relation
58
- else
59
- join_dependency = construct_join_dependency_for_association_find
60
- relation = construct_relation_for_association_find(join_dependency)
61
- end
50
+ relation = ::Rails.version >= "5.2" ?
51
+ apply_join_dependency(eager_loading: false) :
52
+ apply_join_dependency(self, construct_join_dependency)
53
+ return false if ::ActiveRecord::NullRelation === relation
62
54
 
63
55
  relation = relation.except(:select, :order).select("1 AS one").limit(1)
64
56
 
@@ -69,12 +61,11 @@ module Switchman
69
61
  relation = relation.where(table[primary_key].eq(conditions)) if conditions != :none
70
62
  end
71
63
 
72
- args = [relation, "#{name} Exists"]
73
- args << relation.bind_values if ::Rails.version >= '4.1'
74
- relation.activate { return true if connection.select_value(*args) }
75
- false
76
- rescue
77
- raise if ::Rails.version >= '4.1' || !(::ActiveRecord::ThrowResult === $!)
64
+ relation.activate do |shard_rel|
65
+ return true if ::Rails.version >= "5.2" ?
66
+ connection.select_value(shard_rel.arel, "#{name} Exists") :
67
+ connection.select_value(shard_rel, "#{name} Exists", shard_rel.bound_attributes)
68
+ end
78
69
  false
79
70
  end
80
71
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Switchman
2
4
  module ActiveRecord
3
5
  module LogSubscriber
@@ -8,35 +10,28 @@ module Switchman
8
10
 
9
11
  payload = event.payload
10
12
 
11
- return if 'SCHEMA'.freeze == payload[:name]
13
+ return if ::ActiveRecord::LogSubscriber::IGNORE_PAYLOAD_NAMES.include?(payload[:name])
12
14
 
13
- name = '%s (%.1fms)'.freeze % [payload[:name], event.duration]
15
+ name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
16
+ name = "CACHE #{name}" if payload[:cached]
14
17
  sql = payload[:sql].squeeze(' '.freeze)
15
18
  binds = nil
16
19
  shard = payload[:shard]
17
20
  shard = " [#{shard[:database_server_id]}:#{shard[:id]} #{shard[:env]}]" if shard
18
21
 
19
22
  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
23
+ use_old_format = (::Rails.version < '5.1.5')
24
+ args = use_old_format ?
25
+ [payload[:binds], payload[:type_casted_binds]] :
26
+ [payload[:type_casted_binds]]
27
+ casted_params = type_casted_binds(*args)
28
+ binds = " " + payload[:binds].zip(casted_params).map { |attr, value|
29
+ render_bind(attr, value)
26
30
  }.inspect
27
31
  end
28
32
 
29
- if ::Rails.version >= '5'
30
- name = colorize_payload_name(name, payload[:name])
31
- sql = color(sql, sql_color(sql), true)
32
- else
33
- if odd?
34
- name = color(name, self.class::CYAN, true)
35
- sql = color(sql, nil, true)
36
- else
37
- name = color(name, self.class::MAGENTA, true)
38
- end
39
- end
33
+ name = colorize_payload_name(name, payload[:name])
34
+ sql = color(sql, sql_color(sql), true)
40
35
 
41
36
  debug " #{name} #{sql}#{binds}#{shard}"
42
37
  end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Switchman
4
+ module ActiveRecord
5
+ module Migration
6
+ module Compatibility
7
+ module V5_0
8
+ def create_table(*args, **options)
9
+ unless options.key?(:id)
10
+ options[:id] = :bigserial
11
+ end
12
+ if block_given?
13
+ super do |td|
14
+ yield td
15
+ end
16
+ else
17
+ super
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ def connection
24
+ conn = super
25
+ if conn.shard != ::ActiveRecord::Base.connection_pool.current_pool.shard
26
+ ::ActiveRecord::Base.connection_pool.current_pool.switch_database(conn)
27
+ end
28
+ conn
29
+ end
30
+ end
31
+
32
+ module Migrator
33
+ # significant change: hash shard id, not database name
34
+ def generate_migrator_advisory_lock_id
35
+ shard_name_hash = Zlib.crc32(Shard.current.name)
36
+ ::ActiveRecord::Migrator::MIGRATOR_SALT * shard_name_hash
37
+ end
38
+
39
+ if ::Rails.version >= '6.0'
40
+ # copy/paste from Rails 6.1
41
+ def with_advisory_lock
42
+ lock_id = generate_migrator_advisory_lock_id
43
+
44
+ with_advisory_lock_connection do |connection|
45
+ got_lock = connection.get_advisory_lock(lock_id)
46
+ raise ::ActiveRecord::ConcurrentMigrationError unless got_lock
47
+ load_migrated # reload schema_migrations to be sure it wasn't changed by another process before we got the lock
48
+ yield
49
+ ensure
50
+ if got_lock && !connection.release_advisory_lock(lock_id)
51
+ raise ::ActiveRecord::ConcurrentMigrationError.new(
52
+ ::ActiveRecord::ConcurrentMigrationError::RELEASE_LOCK_FAILED_MESSAGE
53
+ )
54
+ end
55
+ end
56
+ end
57
+
58
+ # significant change: strip out prefer_secondary from config
59
+ def with_advisory_lock_connection
60
+ pool = ::ActiveRecord::ConnectionAdapters::ConnectionHandler.new.establish_connection(
61
+ ::ActiveRecord::Base.connection_config.except(:prefer_secondary)
62
+ )
63
+
64
+ pool.with_connection { |connection| yield(connection) }
65
+ ensure
66
+ pool&.disconnect!
67
+ end
68
+ end
69
+ end
70
+
71
+ module MigrationContext
72
+ def migrations
73
+ return @migrations if instance_variable_defined?(:@migrations)
74
+ migrations_cache = Thread.current[:migrations_cache] ||= {}
75
+ key = Digest::MD5.hexdigest(migration_files.sort.join(','))
76
+ @migrations = migrations_cache[key] ||= super
77
+ end
78
+ end
79
+ end
80
+ end
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Switchman
2
4
  module ActiveRecord
3
5
  module ModelSchema
4
6
  module ClassMethods
5
7
  def quoted_table_name
6
8
  @quoted_table_name ||= {}
7
- @quoted_table_name[Shard.current.id] ||= connection.quote_table_name(table_name)
9
+ @quoted_table_name[Shard.current(shard_category).id] ||= connection.quote_table_name(table_name)
8
10
  end
9
11
  end
10
12
  end
@@ -1,10 +1,18 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Switchman
2
4
  module ActiveRecord
3
5
  module Persistence
4
6
  # touch reads the id attribute directly, so it's not relative to the current shard
5
- def touch(*)
7
+ def touch(*, **)
6
8
  shard.activate(self.class.shard_category) { super }
7
9
  end
10
+
11
+ if ::Rails.version >= '5.2'
12
+ def update_columns(*)
13
+ shard.activate(self.class.shard_category) { super }
14
+ end
15
+ end
8
16
  end
9
17
  end
10
18
  end