switchman 4.0.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.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/lib/switchman/active_record/abstract_adapter.rb +10 -8
  3. data/lib/switchman/active_record/associations.rb +44 -60
  4. data/lib/switchman/active_record/attribute_methods.rb +34 -27
  5. data/lib/switchman/active_record/base.rb +12 -61
  6. data/lib/switchman/active_record/calculations.rb +38 -49
  7. data/lib/switchman/active_record/connection_pool.rb +37 -23
  8. data/lib/switchman/active_record/database_configurations.rb +7 -19
  9. data/lib/switchman/active_record/finder_methods.rb +21 -45
  10. data/lib/switchman/active_record/log_subscriber.rb +1 -10
  11. data/lib/switchman/active_record/migration.rb +14 -33
  12. data/lib/switchman/active_record/persistence.rb +1 -1
  13. data/lib/switchman/active_record/postgresql_adapter.rb +26 -12
  14. data/lib/switchman/active_record/query_cache.rb +22 -42
  15. data/lib/switchman/active_record/query_methods.rb +63 -22
  16. data/lib/switchman/active_record/reflection.rb +9 -2
  17. data/lib/switchman/active_record/relation.rb +64 -5
  18. data/lib/switchman/active_record/spawn_methods.rb +1 -5
  19. data/lib/switchman/active_record/statement_cache.rb +2 -2
  20. data/lib/switchman/active_record/table_definition.rb +1 -1
  21. data/lib/switchman/active_record/test_fixtures.rb +71 -35
  22. data/lib/switchman/arel.rb +0 -25
  23. data/lib/switchman/database_server.rb +5 -9
  24. data/lib/switchman/engine.rb +10 -5
  25. data/lib/switchman/shard.rb +10 -12
  26. data/lib/switchman/sharded_instrumenter.rb +8 -2
  27. data/lib/switchman/test_helper.rb +1 -1
  28. data/lib/switchman/version.rb +1 -1
  29. data/lib/tasks/switchman.rake +1 -1
  30. metadata +25 -159
@@ -3,29 +3,11 @@
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
- connection unless @schemas
7
+ connection_method = (::Rails.version < "7.2") ? :connection : :lease_connection
8
+ send(connection_method) unless @schemas
27
9
  # default shard will not switch databases immediately, so it won't be set yet
28
- @schemas ||= connection.current_schemas
10
+ @schemas ||= send(connection_method).current_schemas
29
11
  @schemas.first
30
12
  end
31
13
 
@@ -43,8 +25,36 @@ module Switchman
43
25
  conn
44
26
  end
45
27
 
28
+ unless ::Rails.version < "7.2"
29
+ def active_connection(switch_shard: true)
30
+ conn = super()
31
+ return nil if conn.nil?
32
+ raise Errors::NonExistentShardError if current_shard.new_record?
33
+
34
+ switch_database(conn) if conn.shard != current_shard && switch_shard
35
+ conn
36
+ end
37
+
38
+ def lease_connection(switch_shard: true)
39
+ conn = super()
40
+ raise Errors::NonExistentShardError if current_shard.new_record?
41
+
42
+ switch_database(conn) if conn.shard != current_shard && switch_shard
43
+ conn
44
+ end
45
+
46
+ def with_connection(switch_shard: true, **kwargs)
47
+ super(**kwargs) do |conn|
48
+ raise Errors::NonExistentShardError if current_shard.new_record?
49
+
50
+ switch_database(conn) if conn.shard != current_shard && switch_shard
51
+ yield conn
52
+ end
53
+ end
54
+ end
55
+
46
56
  def release_connection(with_id = Thread.current)
47
- super(with_id)
57
+ super
48
58
 
49
59
  flush
50
60
  end
@@ -60,7 +70,11 @@ module Switchman
60
70
  private
61
71
 
62
72
  def current_shard
63
- 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
64
78
  end
65
79
 
66
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
@@ -4,7 +4,7 @@ module Switchman
4
4
  module ActiveRecord
5
5
  module FinderMethods
6
6
  def find_one(id)
7
- return super(id) unless klass.integral_id?
7
+ return super unless klass.integral_id?
8
8
 
9
9
  if shard_source_value != :implicit
10
10
  current_shard = Shard.current(klass.connection_class_for_self)
@@ -28,70 +28,46 @@ module Switchman
28
28
  if shard
29
29
  shard.activate { super(local_id) }
30
30
  else
31
- super(id)
31
+ super
32
32
  end
33
33
  end
34
34
 
35
35
  def find_some_ordered(ids)
36
36
  current_shard = Shard.current(klass.connection_class_for_self)
37
37
  ids = ids.map { |id| Shard.relative_id_for(id, current_shard, current_shard) }
38
- super(ids)
38
+ super
39
39
  end
40
40
 
41
41
  def find_or_instantiator_by_attributes(match, attributes, *args)
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
45
+ def exists?(conditions = :none)
46
+ return false if @none
49
47
 
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
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
54
 
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
-
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,53 +29,34 @@ 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
- else
35
- ::ActiveRecord::InternalMetadata.new(connection)[:migrator_advisory_lock_id] = shard_name_hash
36
- end
32
+ @internal_metadata[:migrator_advisory_lock_id] = shard_name_hash.to_s
37
33
  shard_name_hash
38
34
  end
39
35
 
40
- # significant change: strip out prefer_secondary from config
41
- def with_advisory_lock_connection
42
- pool = ::ActiveRecord::ConnectionAdapters::ConnectionHandler.new.establish_connection(
43
- ::ActiveRecord::Base.connection_db_config.configuration_hash.except(:prefer_secondary)
44
- )
45
-
46
- pool.with_connection { |connection| yield(connection) } # rubocop:disable Style/ExplicitBlockArgument
47
- ensure
48
- pool&.disconnect!
36
+ def use_advisory_lock?
37
+ super && pending_migrations.any?
49
38
  end
50
39
  end
51
40
 
52
41
  module MigrationContext
53
42
  def migrate(...)
54
- connection = ::ActiveRecord::Base.connection
55
43
  schema_cache_holder = ::ActiveRecord::Base.connection_pool
56
- schema_cache_holder = schema_cache_holder.schema_reflection if ::Rails.version >= "7.1"
57
- previous_schema_cache = if ::Rails.version < "7.1"
58
- schema_cache_holder.get_schema_cache(connection)
59
- else
60
- schema_cache_holder.instance_variable_get(:@cache)
61
- end
44
+ schema_cache_holder = schema_cache_holder.schema_reflection
45
+ previous_schema_cache = schema_cache_holder.instance_variable_get(:@cache)
62
46
 
63
- if ::Rails.version < "7.1"
64
- temporary_schema_cache = ::ActiveRecord::ConnectionAdapters::SchemaCache.new(connection)
47
+ schema_cache_holder.instance_variable_get(:@cache)
65
48
 
66
- reset_column_information
67
- schema_cache_holder.set_schema_cache(temporary_schema_cache)
68
- else
69
- schema_cache_holder.instance_variable_get(:@cache)
70
-
71
- reset_column_information
72
- schema_cache_holder.clear!
73
- end
49
+ reset_column_information
50
+ schema_cache_holder.clear!
74
51
 
75
52
  begin
76
- super(...)
53
+ super
77
54
  ensure
78
- schema_cache_holder.set_schema_cache(previous_schema_cache)
55
+ if ::Rails.version < "7.2"
56
+ schema_cache_holder.set_schema_cache(previous_schema_cache)
57
+ else
58
+ schema_cache_holder.instance_variable_set(:@cache, previous_schema_cache)
59
+ end
79
60
  reset_column_information
80
61
  end
81
62
  end
@@ -24,7 +24,7 @@ module Switchman
24
24
  super
25
25
  end
26
26
 
27
- def create_or_update(**, &block)
27
+ def create_or_update(**, &)
28
28
  writable_shadow_record_warning
29
29
  super
30
30
  end
@@ -73,23 +73,37 @@ module Switchman
73
73
  end
74
74
  end
75
75
 
76
+ module ClassMethods
77
+ def quote_local_table_name(name)
78
+ # postgres quotes tables and columns the same; just pass through
79
+ # (differs from quote_table_name_with_shard below by no logic to
80
+ # explicitly qualify the table)
81
+ quote_column_name(name)
82
+ end
83
+
84
+ def quote_table_name(name, shard: nil)
85
+ # This looks kind of weird at first glance, but older Rails versions do not actually import
86
+ # these methods as class methods.
87
+ shard = self.shard if shard.nil? && ::Rails.version < "7.2" && !@use_local_table_name
88
+
89
+ return quote_local_table_name(name) unless shard
90
+
91
+ name = ::ActiveRecord::ConnectionAdapters::PostgreSQL::Utils.extract_schema_qualified_name(name.to_s)
92
+ name.instance_variable_set(:@schema, shard.name) unless name.schema
93
+ name.quoted
94
+ end
95
+ end
96
+
76
97
  def quote_local_table_name(name)
77
- # postgres quotes tables and columns the same; just pass through
78
- # (differs from quote_table_name_with_shard below by no logic to
79
- # explicitly qualify the table)
80
- quote_column_name(name)
98
+ self.class.quote_local_table_name(name)
81
99
  end
82
100
 
83
101
  def quote_table_name(name)
84
- return quote_local_table_name(name) if @use_local_table_name
85
-
86
- name = ::ActiveRecord::ConnectionAdapters::PostgreSQL::Utils.extract_schema_qualified_name(name.to_s)
87
- name.instance_variable_set(:@schema, shard.name) unless name.schema
88
- name.quoted
102
+ self.class.quote_table_name(name, shard: @use_local_table_name ? nil : shard)
89
103
  end
90
104
 
91
- def with_global_table_name(&block)
92
- with_local_table_name(false, &block)
105
+ def with_global_table_name(&)
106
+ with_local_table_name(false, &)
93
107
  end
94
108
 
95
109
  def with_local_table_name(enable = true) # rubocop:disable Style/OptionalBooleanParameter
@@ -129,7 +143,7 @@ module Switchman
129
143
  end
130
144
 
131
145
  def columns(*)
132
- with_local_table_name(false) { super }
146
+ with_global_table_name { super }
133
147
  end
134
148
  end
135
149
  end
@@ -5,41 +5,15 @@ 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: sql,
17
- binds: binds,
18
- name: 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
15
+ @lock.synchronize do
16
+ if ::Rails.version < "7.2"
43
17
  if (result = @query_cache.delete(key))
44
18
  hit = true
45
19
  @query_cache[key] = result
@@ -47,17 +21,23 @@ module Switchman
47
21
  result = @query_cache[key] = yield
48
22
  @query_cache.shift if @query_cache_max_size && @query_cache.size > @query_cache_max_size
49
23
  end
24
+ else
25
+ hit = true
26
+ result = @query_cache.compute_if_absent(key) do
27
+ hit = false
28
+ yield
29
+ end
50
30
  end
31
+ end
51
32
 
52
- if hit
53
- ::ActiveSupport::Notifications.instrument(
54
- "sql.active_record",
55
- cache_notification_info(sql, name, binds)
56
- )
57
- end
58
-
59
- result.dup
33
+ if hit
34
+ ::ActiveSupport::Notifications.instrument(
35
+ "sql.active_record",
36
+ cache_notification_info(sql, name, binds)
37
+ )
60
38
  end
39
+
40
+ result.dup
61
41
  end
62
42
  end
63
43
  end
@@ -34,13 +34,29 @@ module Switchman
34
34
  end
35
35
 
36
36
  def shard_value=(value)
37
- raise ::ActiveRecord::ImmutableRelation if @loaded
37
+ if @loaded
38
+ error_class = if ::Rails.version < "7.2"
39
+ ::ActiveRecord::ImmutableRelation
40
+ else
41
+ ::ActiveRecord::UnmodifiableRelation
42
+ end
43
+
44
+ raise error_class
45
+ end
38
46
 
39
47
  @values[:shard] = value
40
48
  end
41
49
 
42
50
  def shard_source_value=(value)
43
- raise ::ActiveRecord::ImmutableRelation if @loaded
51
+ if @loaded
52
+ error_class = if ::Rails.version < "7.2"
53
+ ::ActiveRecord::ImmutableRelation
54
+ else
55
+ ::ActiveRecord::UnmodifiableRelation
56
+ end
57
+
58
+ raise error_class
59
+ end
44
60
 
45
61
  @values[:shard_source] = value
46
62
  end
@@ -107,6 +123,10 @@ module Switchman
107
123
 
108
124
  protected
109
125
 
126
+ def arel_columns(columns)
127
+ connection.with_local_table_name { super }
128
+ end
129
+
110
130
  def remove_nonlocal_primary_keys!
111
131
  each_transposable_predicate_value do |value, predicate, _relation, _column, type|
112
132
  next value unless
@@ -243,31 +263,49 @@ module Switchman
243
263
  end
244
264
  end
245
265
 
246
- def arel_columns(columns)
247
- connection.with_local_table_name { super }
248
- end
249
-
250
266
  def arel_column(columns)
251
267
  connection.with_local_table_name { super }
252
268
  end
253
269
 
254
270
  def table_name_matches?(from)
255
- connection.with_global_table_name { super }
271
+ if ::Rails.version < "7.2"
272
+ connection.with_global_table_name { super }
273
+ else
274
+ connection.with_global_table_name do
275
+ table_name = Regexp.escape(table.name)
276
+ # INST: adapter_class -> connection
277
+ quoted_table_name = Regexp.escape(connection.quote_table_name(table.name))
278
+ /(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
279
+ end
280
+ end
281
+ end
282
+
283
+ unless ::Rails.version < "7.2"
284
+ def order_column(field)
285
+ arel_column(field) do |attr_name|
286
+ if attr_name == "count" && !group_values.empty?
287
+ table[attr_name]
288
+ else
289
+ # INST: adapter_class -> connection
290
+ ::Arel.sql(connection.quote_table_name(attr_name), retryable: true)
291
+ end
292
+ end
293
+ end
256
294
  end
257
295
 
258
- def each_predicate(predicates = nil, &block)
259
- return predicates.map(&block) if predicates
296
+ def each_predicate(predicates = nil, &)
297
+ return predicates.map(&) if predicates
260
298
 
261
- each_predicate_cb(:having_clause, :having_clause=, &block)
262
- each_predicate_cb(:where_clause, :where_clause=, &block)
299
+ each_predicate_cb(:having_clause, :having_clause=, &)
300
+ each_predicate_cb(:where_clause, :where_clause=, &)
263
301
  end
264
302
 
265
- def each_predicate_cb(clause_getter, clause_setter, &block)
303
+ def each_predicate_cb(clause_getter, clause_setter, &)
266
304
  old_clause = send(clause_getter)
267
305
  old_predicates = old_clause.send(:predicates)
268
306
  return if old_predicates.empty?
269
307
 
270
- new_predicates = old_predicates.map(&block)
308
+ new_predicates = old_predicates.map(&)
271
309
  return if new_predicates == old_predicates
272
310
 
273
311
  new_clause = old_clause.dup
@@ -289,7 +327,10 @@ module Switchman
289
327
 
290
328
  next predicate if new_left == old_left && new_right == old_right
291
329
 
292
- next predicate.class.new predicate.expr.class.new(new_left, new_right)
330
+ next predicate.class.new predicate.expr.class.new(new_left, new_right) if ::Rails.version < "7.2"
331
+
332
+ next predicate.class.new predicate.expr.class.new([new_left, new_right])
333
+
293
334
  when ::Arel::Nodes::SelectStatement
294
335
  new_cores = predicate.cores.map do |core|
295
336
  next core unless core.is_a?(::Arel::Nodes::SelectCore) # just in case something weird is going on
@@ -344,33 +385,33 @@ module Switchman
344
385
  end
345
386
  end
346
387
 
347
- def each_transposable_predicate_value_cb(node, original_block, &block)
388
+ def each_transposable_predicate_value_cb(node, original_block, &)
348
389
  case node
349
390
  when Array
350
- node.filter_map { |val| each_transposable_predicate_value_cb(val, original_block, &block).presence }
391
+ node.filter_map { |val| each_transposable_predicate_value_cb(val, original_block, &).presence }
351
392
  when ::ActiveModel::Attribute
352
393
  old_value = node.value_before_type_cast
353
- new_value = each_transposable_predicate_value_cb(old_value, original_block, &block)
394
+ new_value = each_transposable_predicate_value_cb(old_value, original_block, &)
354
395
 
355
396
  (old_value == new_value) ? node : node.class.new(node.name, new_value, node.type)
356
397
  when ::Arel::Nodes::And
357
398
  old_value = node.children
358
- new_value = each_transposable_predicate_value_cb(old_value, original_block, &block)
399
+ new_value = each_transposable_predicate_value_cb(old_value, original_block, &)
359
400
 
360
401
  (old_value == new_value) ? node : node.class.new(new_value)
361
402
  when ::Arel::Nodes::BindParam
362
403
  old_value = node.value
363
- new_value = each_transposable_predicate_value_cb(old_value, original_block, &block)
404
+ new_value = each_transposable_predicate_value_cb(old_value, original_block, &)
364
405
 
365
406
  (old_value == new_value) ? node : node.class.new(new_value)
366
407
  when ::Arel::Nodes::Casted
367
408
  old_value = node.value
368
- new_value = each_transposable_predicate_value_cb(old_value, original_block, &block)
409
+ new_value = each_transposable_predicate_value_cb(old_value, original_block, &)
369
410
 
370
411
  (old_value == new_value) ? node : node.class.new(new_value, node.attribute)
371
412
  when ::Arel::Nodes::HomogeneousIn
372
413
  old_value = node.values
373
- new_value = each_transposable_predicate_value_cb(old_value, original_block, &block)
414
+ new_value = each_transposable_predicate_value_cb(old_value, original_block, &)
374
415
 
375
416
  # switch to a regular In, so that Relation::WhereClause#contradiction? knows about it
376
417
  if new_value.empty?
@@ -381,7 +422,7 @@ module Switchman
381
422
  end
382
423
  when ::Arel::Nodes::Binary
383
424
  old_value = node.right
384
- new_value = each_transposable_predicate_value_cb(old_value, original_block, &block)
425
+ new_value = each_transposable_predicate_value_cb(old_value, original_block, &)
385
426
 
386
427
  (old_value == new_value) ? node : node.class.new(node.left, new_value)
387
428
  when ::Arel::Nodes::SelectStatement
@@ -28,11 +28,18 @@ module Switchman
28
28
  # this technically belongs on AssociationReflection, but we put it on
29
29
  # ThroughReflection as well, instead of delegating to its internal
30
30
  # HasManyAssociation, losing its proper `klass`
31
- def association_scope_cache(klass, owner, &block)
31
+ def association_scope_cache(klass, owner, &)
32
32
  key = self
33
33
  key = [key, owner._read_attribute(@foreign_type)] if polymorphic?
34
34
  key = [key, shard(owner).id].flatten
35
- klass.cached_find_by_statement(key, &block)
35
+
36
+ if ::Rails.version < "7.2"
37
+ klass.cached_find_by_statement(key, &)
38
+ else
39
+ klass.with_connection do |connection|
40
+ klass.cached_find_by_statement(connection, key, &)
41
+ end
42
+ end
36
43
  end
37
44
  end
38
45