switchman 4.1.0 → 4.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e03940e1a5f3d0e7b933d99b3cbd68e3998b55f14ac0e7eec91ef81a193445e1
4
- data.tar.gz: 923793689418332ab6ce2ffef2981567bd74b6358916794d958172638b0ec160
3
+ metadata.gz: 4676a4e97375d1094b6775fa2a10f907218bbd2cb73f365c801a698822f779a0
4
+ data.tar.gz: 4f88954e9074fcd122ecbb8d965e1b16edb63c63d69fa5311f7e0a97e400c3d5
5
5
  SHA512:
6
- metadata.gz: 6950ac12cf6532f98e3674ded8890e7bb2f9ef50f33657220096de50508715ab047250c163546dc7396c61d15197ee2b517ae342f84cbe007ae22a217ed7751f
7
- data.tar.gz: c45df067998ec5868bfe2c6a0f52608ffd9de9a19e2ce55ba55fc9986e077833df9964c5783c0f394a9683cb21a610e7f2a781d88e690fbc2bfeef5e8e352826
6
+ metadata.gz: f043f994b2014ce1c980d6fe73a8137a5b497831bbe87592e0c3140e777d10bcbe1460216e3dcf7cc4d1c71cdbda98d3c2b0e5a6a9859f48120cbc2b9425c24f
7
+ data.tar.gz: 943659f21c0c99d8b1db896009a24601a8e698f13d0b2920e3532829a1bf0b1c5a319487d766d24c47b33a975b597ec4dc57e051ee0376e4b4dc6405515e38c8
@@ -19,18 +19,20 @@ module Switchman
19
19
 
20
20
  def initialize(*args)
21
21
  super
22
- @instrumenter = Switchman::ShardedInstrumenter.new(@instrumenter, self)
22
+
23
+ @instrumenter = Switchman::ShardedInstrumenter.new(@instrumenter, self) if ::Rails.version < "8.0"
24
+
23
25
  @last_query_at = Time.now
24
26
  end
25
27
 
26
- def quote_local_table_name(name)
27
- quote_table_name(name)
28
+ if ::Rails.version >= "8.0"
29
+ def instrumenter # :nodoc:
30
+ @instrumenter ||= Switchman::ShardedInstrumenter.new(::ActiveSupport::Notifications.instrumenter, self)
31
+ end
28
32
  end
29
33
 
30
- if ::Rails.version < "7.1"
31
- def schema_migration
32
- ::ActiveRecord::SchemaMigration
33
- end
34
+ def quote_local_table_name(name)
35
+ quote_table_name(name)
34
36
  end
35
37
 
36
38
  protected
@@ -23,7 +23,7 @@ module Switchman
23
23
  end
24
24
 
25
25
  module CollectionAssociation
26
- def find_target
26
+ def find_target(async: false)
27
27
  shards = if reflection.options[:multishard] && owner.respond_to?(:associated_shards)
28
28
  owner.associated_shards
29
29
  else
@@ -38,6 +38,8 @@ module Switchman
38
38
  # otherwise, the super call will set the shard_value to the object, causing it to iterate too many times
39
39
  # over the associated shards
40
40
  scope.shard(Shard.current(scope.klass.connection_class_for_self), :association).to_a
41
+ elsif ::Rails.version < "8.0"
42
+ super()
41
43
  else
42
44
  super
43
45
  end
@@ -251,78 +253,60 @@ module Switchman
251
253
  self.shard_source_value = :association
252
254
  end
253
255
 
254
- def shard(*args)
255
- scope.shard(*args)
256
+ def shard(*)
257
+ scope.shard(*)
256
258
  end
257
259
  end
258
260
 
259
261
  module AutosaveAssociation
260
- if ::Rails.version < "7.1"
261
- def association_foreign_key_changed?(reflection, record, key)
262
- return false if reflection.through_reflection?
262
+ def association_foreign_key_changed?(reflection, record, key)
263
+ return false if reflection.through_reflection?
263
264
 
264
- # have to use send instead of _read_attribute because sharding
265
- record.has_attribute?(reflection.foreign_key) && record.send(reflection.foreign_key) != key
266
- end
267
-
268
- def save_belongs_to_association(reflection)
269
- # this seems counter-intuitive, but the autosave code will assign to attribute bypassing switchman,
270
- # after reading the id attribute _without_ bypassing switchman. So we need Shard.current for the
271
- # category of the associated record to match Shard.current for the category of self
272
- shard.activate(connection_class_for_self_for_reflection(reflection)) { super }
273
- end
274
- else
275
- def association_foreign_key_changed?(reflection, record, key)
276
- return false if reflection.through_reflection?
265
+ foreign_key = Array(reflection.foreign_key)
266
+ return false unless foreign_key.all? { |k| record._has_attribute?(k) }
277
267
 
278
- foreign_key = Array(reflection.foreign_key)
279
- return false unless foreign_key.all? { |k| record._has_attribute?(k) }
268
+ # have to use send instead of _read_attribute because sharding
269
+ foreign_key.map { |k| record.send(k) } != Array(key)
270
+ end
280
271
 
281
- # have to use send instead of _read_attribute because sharding
282
- foreign_key.map { |k| record.send(k) } != Array(key)
283
- end
272
+ def save_belongs_to_association(reflection)
273
+ association = association_instance_get(reflection.name)
274
+ return unless association&.loaded? && !association.stale_target?
284
275
 
285
- def save_belongs_to_association(reflection)
286
- association = association_instance_get(reflection.name)
287
- return unless association&.loaded? && !association.stale_target?
276
+ record = association.load_target
277
+ return unless record && !record.destroyed?
288
278
 
289
- record = association.load_target
290
- return unless record && !record.destroyed?
279
+ autosave = reflection.options[:autosave]
291
280
 
292
- autosave = reflection.options[:autosave]
281
+ if autosave && record.marked_for_destruction?
282
+ foreign_key = Array(reflection.foreign_key)
283
+ foreign_key.each { |key| self[key] = nil }
284
+ record.destroy
285
+ elsif autosave != false
286
+ saved = record.save(validate: !autosave) if record.new_record? || (autosave && record.changed_for_autosave?)
293
287
 
294
- if autosave && record.marked_for_destruction?
288
+ if association.updated?
289
+ primary_key = Array(compute_primary_key(reflection, record)).map(&:to_s)
295
290
  foreign_key = Array(reflection.foreign_key)
296
- foreign_key.each { |key| self[key] = nil }
297
- record.destroy
298
- elsif autosave != false
299
- if record.new_record? || (autosave && record.changed_for_autosave?)
300
- saved = record.save(validate: !autosave)
301
- end
302
291
 
303
- if association.updated?
304
- primary_key = Array(compute_primary_key(reflection, record)).map(&:to_s)
305
- foreign_key = Array(reflection.foreign_key)
306
-
307
- primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
308
- primary_key_foreign_key_pairs.each do |pk, fk|
309
- # Notable change: add relative_id_for here
310
- association_id = if record.class.sharded_column?(pk)
311
- Shard.relative_id_for(
312
- record._read_attribute(pk),
313
- record.shard,
314
- shard
315
- )
316
- else
317
- record._read_attribute(pk)
318
- end
319
- self[fk] = association_id unless self[fk] == association_id
320
- end
321
- association.loaded!
292
+ primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
293
+ primary_key_foreign_key_pairs.each do |pk, fk|
294
+ # Notable change: add relative_id_for here
295
+ association_id = if record.class.sharded_column?(pk)
296
+ Shard.relative_id_for(
297
+ record._read_attribute(pk),
298
+ record.shard,
299
+ shard
300
+ )
301
+ else
302
+ record._read_attribute(pk)
303
+ end
304
+ self[fk] = association_id unless self[fk] == association_id
322
305
  end
323
-
324
- saved if autosave
306
+ association.loaded!
325
307
  end
308
+
309
+ saved if autosave
326
310
  end
327
311
  end
328
312
  end
@@ -55,13 +55,13 @@ module Switchman
55
55
  raise if connection.open_transactions.positive?
56
56
  end
57
57
 
58
- def define_cached_method(owner, name, namespace:, as:, &block)
58
+ def define_cached_method(owner, name, namespace:, as:, &)
59
59
  if ::Rails.version < "7.1.4"
60
60
  # https://github.com/rails/rails/commit/a2a12fc2e3f4e6d06f81d4c74c88f8e6b3369ee6#diff-5b59ece6d9396b596f06271cec0ea726e3360911383511c49b1a66f454bfc2b6L30
61
61
  # These arguments were effectively swapped in Rails 7.1.4, so previous versions need them reversed
62
- owner.define_cached_method(as, namespace:, as: name, &block)
62
+ owner.define_cached_method(as, namespace:, as: name, &)
63
63
  else
64
- owner.define_cached_method(name, namespace:, as:, &block)
64
+ owner.define_cached_method(name, namespace:, as:, &)
65
65
  end
66
66
  end
67
67
 
@@ -53,11 +53,7 @@ module Switchman
53
53
  end
54
54
 
55
55
  def clear_query_caches_for_current_thread
56
- pools = if ::Rails.version < "7.1"
57
- ::ActiveRecord::Base.connection_handler.connection_pool_list
58
- else
59
- ::ActiveRecord::Base.connection_handler.connection_pool_list(:all)
60
- end
56
+ pools = ::ActiveRecord::Base.connection_handler.connection_pool_list(:all)
61
57
  pools.each do |pool|
62
58
  if ::Rails.version < "7.2"
63
59
  pool.connection(switch_shard: false).clear_query_cache if pool.active_connection?
@@ -145,7 +141,7 @@ module Switchman
145
141
  klass.singleton_class.prepend(ClassMethods)
146
142
  klass.singleton_class.prepend(Switchman::ActiveRecord::Relation::InsertUpsertAll) if ::Rails.version < "7.2"
147
143
  klass.scope :non_shadow, lambda { |key = primary_key|
148
- where(key => (QueryMethods::NonTransposingValue.new(0)..
144
+ where(key => (QueryMethods::NonTransposingValue.new(0)...
149
145
  QueryMethods::NonTransposingValue.new(Shard::IDS_PER_SHARD)))
150
146
  }
151
147
  klass.scope :shadow, lambda { |key = primary_key|
@@ -31,7 +31,7 @@ module Switchman
31
31
  if operation == "average"
32
32
  result = calculate_simple_average(column_name, distinct)
33
33
  else
34
- result = activate do |relation|
34
+ result = activate(count: operation == "count") do |relation|
35
35
  relation.call_super(:execute_simple_calculation, Calculations, operation, column_name, distinct)
36
36
  end
37
37
  if result.is_a?(Array)
@@ -95,7 +95,7 @@ module Switchman
95
95
  opts[:group_columns].each do |aliaz, _type, group_column_name|
96
96
  if opts[:associated] && (aliaz == opts[:group_aliases].first)
97
97
  row[aliaz] = key_records[Shard.relative_id_for(row[aliaz], shard, target_shard)]
98
- elsif group_column_name && @klass.sharded_column?(group_column_name)
98
+ elsif group_column_name && klass.sharded_column?(group_column_name)
99
99
  row[aliaz] = Shard.relative_id_for(row[aliaz], shard, target_shard)
100
100
  end
101
101
  end
@@ -106,41 +106,39 @@ module Switchman
106
106
  compact_grouped_calculation_rows(rows, opts)
107
107
  end
108
108
 
109
- if ::Rails.version >= "7.1"
110
- def ids
111
- return super unless klass.sharded_primary_key?
109
+ def ids
110
+ return super unless klass.sharded_primary_key?
112
111
 
113
- if loaded?
114
- result = records.map do |record|
115
- Shard.relative_id_for(record._read_attribute(primary_key),
116
- record.shard,
117
- Shard.current(klass.connection_class_for_self))
118
- end
119
- return @async ? Promise::Complete.new(result) : result
112
+ if loaded?
113
+ result = records.map do |record|
114
+ Shard.relative_id_for(record._read_attribute(primary_key),
115
+ record.shard,
116
+ Shard.current(klass.connection_class_for_self))
120
117
  end
118
+ return @async ? Promise::Complete.new(result) : result
119
+ end
121
120
 
122
- if has_include?(primary_key)
123
- relation = apply_join_dependency.group(primary_key)
124
- return relation.ids
125
- end
121
+ if has_include?(primary_key)
122
+ relation = apply_join_dependency.group(primary_key)
123
+ return relation.ids
124
+ end
126
125
 
127
- columns = arel_columns([primary_key])
128
- base_shard = Shard.current(klass.connection_class_for_self)
129
- activate do |r|
130
- relation = r.spawn
131
- relation.select_values = columns
132
-
133
- result = if relation.where_clause.contradiction?
134
- ::ActiveRecord::Result.empty
135
- else
136
- skip_query_cache_if_necessary do
137
- klass.connection.select_all(relation, "#{klass.name} Ids", async: @async)
138
- end
126
+ columns = arel_columns([primary_key])
127
+ base_shard = Shard.current(klass.connection_class_for_self)
128
+ activate do |r|
129
+ relation = r.spawn
130
+ relation.select_values = columns
131
+
132
+ result = if relation.where_clause.contradiction?
133
+ ::ActiveRecord::Result.empty
134
+ else
135
+ skip_query_cache_if_necessary do
136
+ klass.connection.select_all(relation, "#{klass.name} Ids", async: @async)
139
137
  end
138
+ end
140
139
 
141
- result.then do |res|
142
- type_cast_pluck_values(res, columns).map { |id| Shard.relative_id_for(id, Shard.current, base_shard) }
143
- end
140
+ result.then do |res|
141
+ type_cast_pluck_values(res, columns).map { |id| Shard.relative_id_for(id, Shard.current, base_shard) }
144
142
  end
145
143
  end
146
144
  end
@@ -161,10 +159,7 @@ module Switchman
161
159
  def grouped_calculation_options(operation, column_name, distinct)
162
160
  opts = { operation:, column_name:, distinct: }
163
161
 
164
- # Rails 7.0.5
165
- if defined?(::ActiveRecord::Calculations::ColumnAliasTracker)
166
- column_alias_tracker = ::ActiveRecord::Calculations::ColumnAliasTracker.new(connection)
167
- end
162
+ column_alias_tracker = ::ActiveRecord::Calculations::ColumnAliasTracker.new(connection)
168
163
 
169
164
  opts[:aggregate_alias] = aggregate_alias_for(operation, column_name, column_alias_tracker)
170
165
  group_attrs = group_values
@@ -179,11 +174,7 @@ module Switchman
179
174
 
180
175
  group_aliases = group_fields.map do |field|
181
176
  field = connection.visitor.compile(field) if ::Arel.arel_node?(field)
182
- if column_alias_tracker
183
- column_alias_tracker.alias_for(field.to_s.downcase)
184
- else
185
- column_alias_for(field.to_s.downcase)
186
- end
177
+ column_alias_tracker.alias_for(field.to_s.downcase)
187
178
  end
188
179
  group_columns = group_aliases.zip(group_fields).map do |aliaz, field|
189
180
  [aliaz, type_for(field), column_name_for(field)]
@@ -202,10 +193,8 @@ module Switchman
202
193
  "count_all"
203
194
  elsif operation == "average"
204
195
  "average"
205
- elsif column_alias_tracker
206
- column_alias_tracker.alias_for("#{operation} #{column_name}")
207
196
  else
208
- column_alias_for("#{operation} #{column_name}")
197
+ column_alias_tracker.alias_for("#{operation} #{column_name}")
209
198
  end
210
199
  end
211
200
 
@@ -3,25 +3,6 @@
3
3
  module Switchman
4
4
  module ActiveRecord
5
5
  module ConnectionPool
6
- if ::Rails.version < "7.1"
7
- def get_schema_cache(connection)
8
- self.schema_cache ||= SharedSchemaCache.get_schema_cache(connection)
9
- self.schema_cache.connection = connection
10
-
11
- self.schema_cache
12
- end
13
-
14
- # rubocop:disable Naming/AccessorMethodName -- override method
15
- def set_schema_cache(cache)
16
- schema_cache = get_schema_cache(cache.connection)
17
-
18
- cache.instance_variables.each do |x|
19
- schema_cache.instance_variable_set(x, cache.instance_variable_get(x))
20
- end
21
- end
22
- # rubocop:enable Naming/AccessorMethodName -- override method
23
- end
24
-
25
6
  def default_schema
26
7
  connection_method = (::Rails.version < "7.2") ? :connection : :lease_connection
27
8
  send(connection_method) unless @schemas
@@ -89,7 +70,11 @@ module Switchman
89
70
  private
90
71
 
91
72
  def current_shard
92
- connection_class.current_switchman_shard
73
+ if ::Rails.version < "8.0"
74
+ connection_class.current_switchman_shard
75
+ else
76
+ connection_descriptor.name.constantize.current_switchman_shard
77
+ end
93
78
  end
94
79
 
95
80
  def tls_key
@@ -7,26 +7,14 @@ module Switchman
7
7
  # since all should point to the same data, even if multiple are writable
8
8
  # (Picks 'primary' since it is guaranteed to exist and switchman handles activating
9
9
  # deploy through other means)
10
- if ::Rails.version < "7.1"
11
- def configs_for(include_replicas: false, name: nil, **)
12
- res = super
13
- if name && !include_replicas
14
- return nil unless name.end_with?("primary")
15
- elsif !include_replicas
16
- return res.select { |config| config.name.end_with?("primary") }
17
- end
18
- res
19
- end
20
- else
21
- def configs_for(include_hidden: false, name: nil, **)
22
- res = super
23
- if name && !include_hidden
24
- return nil unless name.end_with?("primary")
25
- elsif !include_hidden
26
- return res.select { |config| config.name.end_with?("primary") }
27
- end
28
- res
10
+ def configs_for(include_hidden: false, name: nil, **)
11
+ res = super
12
+ if name && !include_hidden
13
+ return nil unless name.end_with?("primary")
14
+ elsif !include_hidden
15
+ return res.select { |config| config.name.end_with?("primary") }
29
16
  end
17
+ res
30
18
  end
31
19
 
32
20
  private
@@ -42,56 +42,32 @@ module Switchman
42
42
  primary_shard.activate { super }
43
43
  end
44
44
 
45
- if ::Rails.version < "7.1"
46
- def exists?(conditions = :none)
47
- conditions = conditions.id if ::ActiveRecord::Base === conditions
48
- return false unless conditions
49
-
50
- relation = apply_join_dependency(eager_loading: false)
51
- return false if ::ActiveRecord::NullRelation === relation
52
-
53
- relation = relation.except(:select, :order).select("1 AS one").limit(1)
54
-
55
- case conditions
56
- when Array, Hash
57
- relation = relation.where(conditions)
58
- else
59
- relation = relation.where(table[primary_key].eq(conditions)) if conditions != :none
60
- end
61
-
62
- relation.activate do |shard_rel|
63
- return true if connection.select_value(shard_rel.arel, "#{name} Exists")
64
- end
65
- false
45
+ def exists?(conditions = :none)
46
+ return false if @none
47
+
48
+ if Base === conditions
49
+ raise ArgumentError, <<-TEXT.squish
50
+ You are passing an instance of ActiveRecord::Base to `exists?`.
51
+ Please pass the id of the object by calling `.id`.
52
+ TEXT
66
53
  end
67
- else
68
- def exists?(conditions = :none)
69
- return false if @none
70
-
71
- if Base === conditions
72
- raise ArgumentError, <<-TEXT.squish
73
- You are passing an instance of ActiveRecord::Base to `exists?`.
74
- Please pass the id of the object by calling `.id`.
75
- TEXT
76
- end
77
54
 
78
- return false if !conditions || limit_value == 0 # rubocop:disable Style/NumericPredicate
55
+ return false if !conditions || limit_value == 0 # rubocop:disable Style/NumericPredicate
79
56
 
80
- if eager_loading?
81
- relation = apply_join_dependency(eager_loading: false)
82
- return relation.exists?(conditions)
83
- end
57
+ if eager_loading?
58
+ relation = apply_join_dependency(eager_loading: false)
59
+ return relation.exists?(conditions)
60
+ end
84
61
 
85
- relation = construct_relation_for_exists(conditions)
86
- return false if relation.where_clause.contradiction?
62
+ relation = construct_relation_for_exists(conditions)
63
+ return false if relation.where_clause.contradiction?
87
64
 
88
- relation.activate do |shard_rel|
89
- return true if skip_query_cache_if_necessary do
90
- connection.select_rows(shard_rel.arel, "#{name} Exists?").size == 1
91
- end
65
+ relation.activate do |shard_rel|
66
+ return true if skip_query_cache_if_necessary do
67
+ connection.select_rows(shard_rel.arel, "#{name} Exists?").size == 1
92
68
  end
93
- false
94
69
  end
70
+ false
95
71
  end
96
72
  end
97
73
  end
@@ -5,11 +5,6 @@ module Switchman
5
5
  module LogSubscriber
6
6
  # sadly, have to completely replace this
7
7
  def sql(event)
8
- if ::Rails.version < "7.1"
9
- self.class.runtime += event.duration
10
- return unless logger.debug?
11
- end
12
-
13
8
  payload = event.payload
14
9
 
15
10
  return if ::ActiveRecord::LogSubscriber::IGNORE_PAYLOAD_NAMES.include?(payload[:name])
@@ -29,11 +24,7 @@ module Switchman
29
24
  end
30
25
 
31
26
  name = colorize_payload_name(name, payload[:name])
32
- sql = if ::Rails.version < "7.1"
33
- color(sql, sql_color(sql), true)
34
- else
35
- color(sql, sql_color(sql), bold: true)
36
- end
27
+ sql = color(sql, sql_color(sql), bold: true)
37
28
 
38
29
  debug " #{name} #{sql}#{binds}#{shard}"
39
30
  end
@@ -29,50 +29,25 @@ module Switchman
29
29
  db_name_hash = Zlib.crc32(Shard.current.name)
30
30
  shard_name_hash = ::ActiveRecord::Migrator::MIGRATOR_SALT * db_name_hash
31
31
  # Store in internalmetadata to allow other tools to be able to lock out migrations
32
- if ::Rails.version < "7.1"
33
- ::ActiveRecord::InternalMetadata[:migrator_advisory_lock_id] = shard_name_hash
34
- elsif ::Rails.version < "7.2"
35
- ::ActiveRecord::InternalMetadata.new(connection)[:migrator_advisory_lock_id] = shard_name_hash
36
- else
37
- ::ActiveRecord::InternalMetadata.new(connection.pool)[:migrator_advisory_lock_id] = shard_name_hash
38
- end
32
+ @internal_metadata[:migrator_advisory_lock_id] = shard_name_hash.to_s
39
33
  shard_name_hash
40
34
  end
41
35
 
42
- # significant change: strip out prefer_secondary from config
43
- def with_advisory_lock_connection
44
- pool = ::ActiveRecord::ConnectionAdapters::ConnectionHandler.new.establish_connection(
45
- ::ActiveRecord::Base.connection_db_config.configuration_hash.except(:prefer_secondary)
46
- )
47
-
48
- pool.with_connection { |connection| yield(connection) } # rubocop:disable Style/ExplicitBlockArgument
49
- ensure
50
- pool&.disconnect!
36
+ def use_advisory_lock?
37
+ super && pending_migrations.any?
51
38
  end
52
39
  end
53
40
 
54
41
  module MigrationContext
55
42
  def migrate(...)
56
- connection = ::ActiveRecord::Base.connection
57
43
  schema_cache_holder = ::ActiveRecord::Base.connection_pool
58
- schema_cache_holder = schema_cache_holder.schema_reflection if ::Rails.version >= "7.1"
59
- previous_schema_cache = if ::Rails.version < "7.1"
60
- schema_cache_holder.get_schema_cache(connection)
61
- else
62
- schema_cache_holder.instance_variable_get(:@cache)
63
- end
64
-
65
- if ::Rails.version < "7.1"
66
- temporary_schema_cache = ::ActiveRecord::ConnectionAdapters::SchemaCache.new(connection)
44
+ schema_cache_holder = schema_cache_holder.schema_reflection
45
+ previous_schema_cache = schema_cache_holder.instance_variable_get(:@cache)
67
46
 
68
- reset_column_information
69
- schema_cache_holder.set_schema_cache(temporary_schema_cache)
70
- else
71
- schema_cache_holder.instance_variable_get(:@cache)
47
+ schema_cache_holder.instance_variable_get(:@cache)
72
48
 
73
- reset_column_information
74
- schema_cache_holder.clear!
75
- end
49
+ reset_column_information
50
+ schema_cache_holder.clear!
76
51
 
77
52
  begin
78
53
  super
@@ -5,67 +5,39 @@ module Switchman
5
5
  module QueryCache
6
6
  private
7
7
 
8
- if ::Rails.version < "7.1"
9
- def cache_sql(sql, name, binds)
10
- # have to include the shard id in the cache key because of switching dbs on the same connection
11
- sql = "#{shard.id}::#{sql}"
12
- @lock.synchronize do
13
- result =
14
- if query_cache[sql].key?(binds)
15
- args = {
16
- sql:,
17
- binds:,
18
- name:,
19
- connection_id: object_id,
20
- cached: true,
21
- type_casted_binds: -> { type_casted_binds(binds) }
22
- }
23
- ::ActiveSupport::Notifications.instrument(
24
- "sql.active_record",
25
- args
26
- )
27
- query_cache[sql][binds]
28
- else
29
- query_cache[sql][binds] = yield
30
- end
31
- result.dup
32
- end
33
- end
34
- else
35
- def cache_sql(sql, name, binds)
36
- # have to include the shard id in the cache key because of switching dbs on the same connection
37
- sql = "#{shard.id}::#{sql}"
38
- key = binds.empty? ? sql : [sql, binds]
39
- result = nil
40
- hit = false
8
+ def cache_sql(sql, name, binds)
9
+ # have to include the shard id in the cache key because of switching dbs on the same connection
10
+ sql = "#{shard.id}::#{sql}"
11
+ key = binds.empty? ? sql : [sql, binds]
12
+ result = nil
13
+ hit = false
41
14
 
42
- @lock.synchronize do
43
- if ::Rails.version < "7.2"
44
- if (result = @query_cache.delete(key))
45
- hit = true
46
- @query_cache[key] = result
47
- else
48
- result = @query_cache[key] = yield
49
- @query_cache.shift if @query_cache_max_size && @query_cache.size > @query_cache_max_size
50
- end
51
- else
15
+ @lock.synchronize do
16
+ if ::Rails.version < "7.2"
17
+ if (result = @query_cache.delete(key))
52
18
  hit = true
53
- result = @query_cache.compute_if_absent(key) do
54
- hit = false
55
- yield
56
- end
19
+ @query_cache[key] = result
20
+ else
21
+ result = @query_cache[key] = yield
22
+ @query_cache.shift if @query_cache_max_size && @query_cache.size > @query_cache_max_size
23
+ end
24
+ else
25
+ hit = true
26
+ result = @query_cache.compute_if_absent(key) do
27
+ hit = false
28
+ yield
57
29
  end
58
30
  end
31
+ end
59
32
 
60
- if hit
61
- ::ActiveSupport::Notifications.instrument(
62
- "sql.active_record",
63
- cache_notification_info(sql, name, binds)
64
- )
65
- end
66
-
67
- result.dup
33
+ if hit
34
+ ::ActiveSupport::Notifications.instrument(
35
+ "sql.active_record",
36
+ cache_notification_info(sql, name, binds)
37
+ )
68
38
  end
39
+
40
+ result.dup
69
41
  end
70
42
  end
71
43
  end
@@ -163,7 +163,7 @@ module Switchman
163
163
  end
164
164
  end
165
165
 
166
- def activate(unordered: false, &block)
166
+ def activate(count: false, unordered: false, &block)
167
167
  shards = all_shards
168
168
  if Array === shards && shards.length == 1
169
169
  if !loaded? && shard_value != shards.first
@@ -189,7 +189,13 @@ module Switchman
189
189
 
190
190
  shard_results = relation.activate(&block)
191
191
 
192
- if shard_results.present? && !unordered
192
+ if shard_results.present? && count
193
+ unless shard_results.is_a?(Integer)
194
+ raise "expected integer result for count, got #{shard_results.class.name}"
195
+ end
196
+
197
+ result_count += shard_results
198
+ elsif shard_results.present? && !unordered
193
199
  can_order ||= can_order_cross_shard_results? unless order_values.empty?
194
200
  raise OrderOnMultiShardQuery if !can_order && !order_values.empty? && result_count.positive?
195
201
 
@@ -29,14 +29,14 @@ module Switchman
29
29
  params, connection = args
30
30
  klass = @klass
31
31
  target_shard = nil
32
- if (primary_index = bind_map.primary_value_index)
32
+ if (primary_index = @bind_map.primary_value_index)
33
33
  primary_value = params[primary_index]
34
34
  target_shard = Shard.local_id_for(primary_value)[1]
35
35
  end
36
36
  current_shard = Shard.current(klass.connection_class_for_self)
37
37
  target_shard ||= current_shard
38
38
 
39
- bind_values = bind_map.bind(params, current_shard, target_shard)
39
+ bind_values = @bind_map.bind(params, current_shard, target_shard)
40
40
 
41
41
  target_shard.activate(klass.connection_class_for_self) do
42
42
  sql = qualified_query_builder(target_shard, klass).sql_for(bind_values, connection)
@@ -15,17 +15,13 @@ module Switchman
15
15
  # Code adapted from the code in rails proper
16
16
  @connection_subscriber =
17
17
  ::ActiveSupport::Notifications.subscribe("!connection.active_record") do |_, _, _, _, payload|
18
- spec_name = if ::Rails.version < "7.1"
19
- payload[:spec_name] if payload.key?(:spec_name)
20
- elsif payload.key?(:connection_name)
21
- payload[:connection_name]
22
- end
18
+ spec_name = (payload[:connection_name] if payload.key?(:connection_name))
23
19
  shard = payload[:shard] if payload.key?(:shard)
24
20
 
25
21
  if spec_name && !FORBIDDEN_DB_ENVS.include?(shard)
26
22
  begin
27
- connection = ::ActiveRecord::Base.connection_handler.retrieve_connection(spec_name, shard: shard)
28
- connection.connect! if ::Rails.version >= "7.1" # eagerly validate the connection
23
+ connection = ::ActiveRecord::Base.connection_handler.retrieve_connection(spec_name, shard:)
24
+ connection.connect! # eagerly validate the connection
29
25
  rescue ::ActiveRecord::ConnectionNotEstablished
30
26
  connection = nil
31
27
  end
@@ -74,7 +70,7 @@ module Switchman
74
70
 
75
71
  # INST: filter by FORBIDDEN_DB_ENVS
76
72
  if connection_name && !FORBIDDEN_DB_ENVS.include?(shard)
77
- pool = ::ActiveRecord::Base.connection_handler.retrieve_connection_pool(connection_name, shard: shard)
73
+ pool = ::ActiveRecord::Base.connection_handler.retrieve_connection_pool(connection_name, shard:)
78
74
  if pool
79
75
  setup_shared_connection_pool
80
76
 
@@ -38,31 +38,6 @@ module Switchman
38
38
  collector << quote_local_table_name(join_name) << "." << quote_column_name(o.name)
39
39
  end
40
40
 
41
- if ::Rails.version < "7.1"
42
- def visit_Arel_Nodes_HomogeneousIn(o, collector)
43
- collector.preparable = false
44
-
45
- collector << quote_local_table_name(o.table_name) << "." << quote_column_name(o.column_name)
46
-
47
- collector << if o.type == :in
48
- " IN ("
49
- else
50
- " NOT IN ("
51
- end
52
-
53
- values = o.casted_values
54
-
55
- if values.empty?
56
- collector << @connection.quote(nil)
57
- else
58
- collector.add_binds(values, o.proc_for_binds, &bind_block)
59
- end
60
-
61
- collector << ")"
62
- collector
63
- end
64
- end
65
-
66
41
  # rubocop:enable Naming/MethodName
67
42
  # rubocop:enable Naming/MethodParameterName
68
43
 
@@ -259,11 +259,7 @@ module Switchman
259
259
  unless schema == false
260
260
  shard.activate do
261
261
  ::ActiveRecord::Base.connection.transaction(requires_new: true) do
262
- if ::Rails.version < "7.1"
263
- ::ActiveRecord::Base.connection.migration_context.migrate
264
- else
265
- ::ActiveRecord::MigrationContext.new(::ActiveRecord::Migrator.migrations_paths).migrate
266
- end
262
+ ::ActiveRecord::MigrationContext.new(::ActiveRecord::Migrator.migrations_paths).migrate
267
263
  end
268
264
 
269
265
  ::ActiveRecord::Base.descendants.reject do |m|
@@ -4,8 +4,6 @@ module Switchman
4
4
  class Engine < ::Rails::Engine
5
5
  isolate_namespace Switchman
6
6
 
7
- # enable Rails 6.1 style connection handling
8
- config.active_record.legacy_connection_handling = false if ::Rails.version < "7.1"
9
7
  config.active_record.writing_role = :primary
10
8
 
11
9
  ::GuardRail.singleton_class.prepend(GuardRail::ClassMethods)
@@ -47,9 +45,7 @@ module Switchman
47
45
  ActiveRecord::Associations::Preloader::Association::LoaderRecords
48
46
  )
49
47
  ::ActiveRecord::ConnectionAdapters::AbstractAdapter.prepend(ActiveRecord::AbstractAdapter)
50
- unless ::Rails.version < "7.1"
51
- ::ActiveRecord::ConnectionAdapters::ConnectionHandler.prepend(ActiveRecord::ConnectionHandler)
52
- end
48
+ ::ActiveRecord::ConnectionAdapters::ConnectionHandler.prepend(ActiveRecord::ConnectionHandler)
53
49
  ::ActiveRecord::ConnectionAdapters::ConnectionPool.prepend(ActiveRecord::ConnectionPool)
54
50
  ::ActiveRecord::ConnectionAdapters::AbstractAdapter.prepend(ActiveRecord::QueryCache)
55
51
  ::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(ActiveRecord::PostgreSQLAdapter)
@@ -204,7 +204,7 @@ module Switchman
204
204
  database_servers = scope.reorder("database_server_id").select(:database_server_id).distinct
205
205
  .filter_map(&:database_server).uniq
206
206
  # nothing to do
207
- return if database_servers.count.zero?
207
+ return if database_servers.none?
208
208
 
209
209
  scopes = database_servers.to_h do |server|
210
210
  [server, scope.merge(server.shards)]
@@ -216,11 +216,7 @@ module Switchman
216
216
  # clear connections prior to forking (no more queries will be executed in the parent,
217
217
  # and we want them gone so that we don't accidentally use them post-fork doing something
218
218
  # silly like dealloc'ing prepared statements)
219
- if ::Rails.version < "7.1"
220
- ::ActiveRecord::Base.clear_all_connections!(nil)
221
- else
222
- ::ActiveRecord::Base.connection_handler.clear_all_connections!(:all)
223
- end
219
+ ::ActiveRecord::Base.connection_handler.clear_all_connections!(:all)
224
220
 
225
221
  parent_process_name = sanitized_process_title
226
222
  ret = ::Parallel.map(scopes, in_processes: (scopes.length > 1) ? parallel : 0) do |server, subscope|
@@ -13,10 +13,16 @@ module Switchman
13
13
  # when we might be doing a query while defining attribute methods,
14
14
  # so just avoid logging then
15
15
  if shard.is_a?(Shard) && Shard.instance_variable_get(:@attribute_methods_generated)
16
+ env = if ::Rails.version < "8.0"
17
+ @shard_host.pool.connection_class&.current_role
18
+ else
19
+ @shard_host.pool.connection_descriptor&.name&.constantize&.current_role
20
+ end
21
+
16
22
  payload[:shard] = {
17
23
  database_server_id: shard.database_server.id,
18
24
  id: shard.id,
19
- env: @shard_host.pool.connection_class&.current_role
25
+ env:
20
26
  }
21
27
  end
22
28
  super
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Switchman
4
- VERSION = "4.1.0"
4
+ VERSION = "4.2.5"
5
5
  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: 4.1.0
4
+ version: 4.2.5
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: 2025-03-21 00:00:00.000000000 Z
13
+ date: 2026-05-28 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activerecord
@@ -18,222 +18,74 @@ dependencies:
18
18
  requirements:
19
19
  - - ">="
20
20
  - !ruby/object:Gem::Version
21
- version: '7.0'
21
+ version: '7.1'
22
22
  - - "<"
23
23
  - !ruby/object:Gem::Version
24
- version: '7.3'
24
+ version: '8.1'
25
25
  type: :runtime
26
26
  prerelease: false
27
27
  version_requirements: !ruby/object:Gem::Requirement
28
28
  requirements:
29
29
  - - ">="
30
30
  - !ruby/object:Gem::Version
31
- version: '7.0'
31
+ version: '7.1'
32
32
  - - "<"
33
33
  - !ruby/object:Gem::Version
34
- version: '7.3'
34
+ version: '8.1'
35
35
  - !ruby/object:Gem::Dependency
36
36
  name: guardrail
37
37
  requirement: !ruby/object:Gem::Requirement
38
38
  requirements:
39
39
  - - "~>"
40
40
  - !ruby/object:Gem::Version
41
- version: 3.0.1
41
+ version: 3.1.0
42
42
  type: :runtime
43
43
  prerelease: false
44
44
  version_requirements: !ruby/object:Gem::Requirement
45
45
  requirements:
46
46
  - - "~>"
47
47
  - !ruby/object:Gem::Version
48
- version: 3.0.1
48
+ version: 3.1.0
49
49
  - !ruby/object:Gem::Dependency
50
50
  name: parallel
51
51
  requirement: !ruby/object:Gem::Requirement
52
52
  requirements:
53
- - - "~>"
53
+ - - ">="
54
54
  - !ruby/object:Gem::Version
55
55
  version: '1.22'
56
+ - - "<"
57
+ - !ruby/object:Gem::Version
58
+ version: '3.0'
56
59
  type: :runtime
57
60
  prerelease: false
58
61
  version_requirements: !ruby/object:Gem::Requirement
59
62
  requirements:
60
- - - "~>"
63
+ - - ">="
61
64
  - !ruby/object:Gem::Version
62
65
  version: '1.22'
66
+ - - "<"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
63
69
  - !ruby/object:Gem::Dependency
64
70
  name: railties
65
71
  requirement: !ruby/object:Gem::Requirement
66
72
  requirements:
67
73
  - - ">="
68
74
  - !ruby/object:Gem::Version
69
- version: '7.0'
75
+ version: '7.1'
70
76
  - - "<"
71
77
  - !ruby/object:Gem::Version
72
- version: '7.3'
78
+ version: '8.1'
73
79
  type: :runtime
74
80
  prerelease: false
75
81
  version_requirements: !ruby/object:Gem::Requirement
76
82
  requirements:
77
83
  - - ">="
78
84
  - !ruby/object:Gem::Version
79
- version: '7.0'
85
+ version: '7.1'
80
86
  - - "<"
81
87
  - !ruby/object:Gem::Version
82
- version: '7.3'
83
- - !ruby/object:Gem::Dependency
84
- name: debug
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - "~>"
88
- - !ruby/object:Gem::Version
89
- version: '1.8'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - "~>"
95
- - !ruby/object:Gem::Version
96
- version: '1.8'
97
- - !ruby/object:Gem::Dependency
98
- name: pg
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - "~>"
102
- - !ruby/object:Gem::Version
103
- version: '1.2'
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - "~>"
109
- - !ruby/object:Gem::Version
110
- version: '1.2'
111
- - !ruby/object:Gem::Dependency
112
- name: rake
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - "~>"
116
- - !ruby/object:Gem::Version
117
- version: '13.0'
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - "~>"
123
- - !ruby/object:Gem::Version
124
- version: '13.0'
125
- - !ruby/object:Gem::Dependency
126
- name: rspec-mocks
127
- requirement: !ruby/object:Gem::Requirement
128
- requirements:
129
- - - "~>"
130
- - !ruby/object:Gem::Version
131
- version: '3.5'
132
- type: :development
133
- prerelease: false
134
- version_requirements: !ruby/object:Gem::Requirement
135
- requirements:
136
- - - "~>"
137
- - !ruby/object:Gem::Version
138
- version: '3.5'
139
- - !ruby/object:Gem::Dependency
140
- name: rspec-rails
141
- requirement: !ruby/object:Gem::Requirement
142
- requirements:
143
- - - "~>"
144
- - !ruby/object:Gem::Version
145
- version: '6.0'
146
- type: :development
147
- prerelease: false
148
- version_requirements: !ruby/object:Gem::Requirement
149
- requirements:
150
- - - "~>"
151
- - !ruby/object:Gem::Version
152
- version: '6.0'
153
- - !ruby/object:Gem::Dependency
154
- name: rubocop
155
- requirement: !ruby/object:Gem::Requirement
156
- requirements:
157
- - - "~>"
158
- - !ruby/object:Gem::Version
159
- version: '1.10'
160
- type: :development
161
- prerelease: false
162
- version_requirements: !ruby/object:Gem::Requirement
163
- requirements:
164
- - - "~>"
165
- - !ruby/object:Gem::Version
166
- version: '1.10'
167
- - !ruby/object:Gem::Dependency
168
- name: rubocop-inst
169
- requirement: !ruby/object:Gem::Requirement
170
- requirements:
171
- - - "~>"
172
- - !ruby/object:Gem::Version
173
- version: '1'
174
- type: :development
175
- prerelease: false
176
- version_requirements: !ruby/object:Gem::Requirement
177
- requirements:
178
- - - "~>"
179
- - !ruby/object:Gem::Version
180
- version: '1'
181
- - !ruby/object:Gem::Dependency
182
- name: rubocop-rake
183
- requirement: !ruby/object:Gem::Requirement
184
- requirements:
185
- - - "~>"
186
- - !ruby/object:Gem::Version
187
- version: '0.5'
188
- type: :development
189
- prerelease: false
190
- version_requirements: !ruby/object:Gem::Requirement
191
- requirements:
192
- - - "~>"
193
- - !ruby/object:Gem::Version
194
- version: '0.5'
195
- - !ruby/object:Gem::Dependency
196
- name: rubocop-rspec
197
- requirement: !ruby/object:Gem::Requirement
198
- requirements:
199
- - - "~>"
200
- - !ruby/object:Gem::Version
201
- version: '3.0'
202
- type: :development
203
- prerelease: false
204
- version_requirements: !ruby/object:Gem::Requirement
205
- requirements:
206
- - - "~>"
207
- - !ruby/object:Gem::Version
208
- version: '3.0'
209
- - !ruby/object:Gem::Dependency
210
- name: rubocop-rspec_rails
211
- requirement: !ruby/object:Gem::Requirement
212
- requirements:
213
- - - "~>"
214
- - !ruby/object:Gem::Version
215
- version: '2.29'
216
- type: :development
217
- prerelease: false
218
- version_requirements: !ruby/object:Gem::Requirement
219
- requirements:
220
- - - "~>"
221
- - !ruby/object:Gem::Version
222
- version: '2.29'
223
- - !ruby/object:Gem::Dependency
224
- name: simplecov
225
- requirement: !ruby/object:Gem::Requirement
226
- requirements:
227
- - - "~>"
228
- - !ruby/object:Gem::Version
229
- version: '0.15'
230
- type: :development
231
- prerelease: false
232
- version_requirements: !ruby/object:Gem::Requirement
233
- requirements:
234
- - - "~>"
235
- - !ruby/object:Gem::Version
236
- version: '0.15'
88
+ version: '8.1'
237
89
  description: Sharding
238
90
  email:
239
91
  - cody@instructure.com
@@ -312,14 +164,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
312
164
  requirements:
313
165
  - - ">="
314
166
  - !ruby/object:Gem::Version
315
- version: '3.1'
167
+ version: '3.2'
316
168
  required_rubygems_version: !ruby/object:Gem::Requirement
317
169
  requirements:
318
170
  - - ">="
319
171
  - !ruby/object:Gem::Version
320
172
  version: '0'
321
173
  requirements: []
322
- rubygems_version: 3.3.27
174
+ rubygems_version: 3.4.19
323
175
  signing_key:
324
176
  specification_version: 4
325
177
  summary: Rails sharding magic