switchman 3.3.1 → 4.0.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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +15 -14
  3. data/db/migrate/20180828183945_add_default_shard_index.rb +1 -1
  4. data/db/migrate/20190114212900_add_unique_name_indexes.rb +10 -4
  5. data/lib/switchman/active_record/abstract_adapter.rb +4 -2
  6. data/lib/switchman/active_record/associations.rb +89 -49
  7. data/lib/switchman/active_record/attribute_methods.rb +72 -34
  8. data/lib/switchman/active_record/base.rb +145 -27
  9. data/lib/switchman/active_record/calculations.rb +96 -49
  10. data/lib/switchman/active_record/connection_handler.rb +18 -0
  11. data/lib/switchman/active_record/connection_pool.rb +24 -3
  12. data/lib/switchman/active_record/database_configurations.rb +37 -15
  13. data/lib/switchman/active_record/finder_methods.rb +44 -14
  14. data/lib/switchman/active_record/log_subscriber.rb +11 -5
  15. data/lib/switchman/active_record/migration.rb +45 -3
  16. data/lib/switchman/active_record/pending_migration_connection.rb +17 -0
  17. data/lib/switchman/active_record/persistence.rb +30 -0
  18. data/lib/switchman/active_record/postgresql_adapter.rb +11 -10
  19. data/lib/switchman/active_record/predicate_builder.rb +2 -2
  20. data/lib/switchman/active_record/query_cache.rb +49 -20
  21. data/lib/switchman/active_record/query_methods.rb +93 -30
  22. data/lib/switchman/active_record/relation.rb +23 -12
  23. data/lib/switchman/active_record/spawn_methods.rb +2 -2
  24. data/lib/switchman/active_record/statement_cache.rb +2 -2
  25. data/lib/switchman/active_record/tasks/database_tasks.rb +6 -1
  26. data/lib/switchman/active_record/test_fixtures.rb +26 -16
  27. data/lib/switchman/active_support/cache.rb +9 -4
  28. data/lib/switchman/arel.rb +34 -18
  29. data/lib/switchman/call_super.rb +2 -8
  30. data/lib/switchman/database_server.rb +69 -31
  31. data/lib/switchman/default_shard.rb +14 -3
  32. data/lib/switchman/engine.rb +29 -22
  33. data/lib/switchman/environment.rb +2 -2
  34. data/lib/switchman/errors.rb +13 -0
  35. data/lib/switchman/guard_rail/relation.rb +1 -2
  36. data/lib/switchman/parallel.rb +6 -6
  37. data/lib/switchman/r_spec_helper.rb +12 -11
  38. data/lib/switchman/shard.rb +180 -68
  39. data/lib/switchman/sharded_instrumenter.rb +3 -3
  40. data/lib/switchman/shared_schema_cache.rb +11 -0
  41. data/lib/switchman/standard_error.rb +4 -0
  42. data/lib/switchman/test_helper.rb +2 -2
  43. data/lib/switchman/version.rb +1 -1
  44. data/lib/switchman.rb +27 -15
  45. data/lib/tasks/switchman.rake +96 -60
  46. metadata +38 -48
@@ -3,6 +3,19 @@
3
3
  module Switchman
4
4
  module ActiveRecord
5
5
  module QueryMethods
6
+ # Use this class to prevent a value from getting transposed across shards
7
+ class NonTransposingValue < SimpleDelegator
8
+ def class
9
+ __getobj__.class
10
+ end
11
+
12
+ def is_a?(other)
13
+ return true if other == NonTransposingValue
14
+
15
+ __getobj__.is_a?(other)
16
+ end
17
+ end
18
+
6
19
  # shard_value is one of:
7
20
  # A shard
8
21
  # An array or relation of shards
@@ -84,6 +97,14 @@ module Switchman
84
97
  super(other.shard(primary_shard))
85
98
  end
86
99
 
100
+ # use a temp variable so that the new where clause is built before self.where_clause is read,
101
+ # since build_where_clause might mutate self.where_clause
102
+ def where!(opts, *rest)
103
+ new_clause = build_where_clause(opts, rest)
104
+ self.where_clause += new_clause
105
+ self
106
+ end
107
+
87
108
  protected
88
109
 
89
110
  def remove_nonlocal_primary_keys!
@@ -93,7 +114,7 @@ module Switchman
93
114
  predicate.left.relation.klass == klass &&
94
115
  (predicate.is_a?(::Arel::Nodes::Equality) || predicate.is_a?(::Arel::Nodes::HomogeneousIn))
95
116
 
96
- value.is_a?(Integer) && value > Shard::IDS_PER_SHARD ? [] : value
117
+ (value.is_a?(Integer) && value > Shard::IDS_PER_SHARD) ? [] : value
97
118
  end
98
119
  self
99
120
  end
@@ -104,7 +125,9 @@ module Switchman
104
125
  return unless klass.integral_id?
105
126
 
106
127
  primary_key = predicates.detect do |predicate|
107
- (predicate.is_a?(::Arel::Nodes::Binary) || predicate.is_a?(::Arel::Nodes::HomogeneousIn)) &&
128
+ (predicate.is_a?(::Arel::Nodes::Equality) ||
129
+ predicate.is_a?(::Arel::Nodes::In) ||
130
+ predicate.is_a?(::Arel::Nodes::HomogeneousIn)) &&
108
131
  predicate.left.is_a?(::Arel::Attributes::Attribute) &&
109
132
  predicate.left.relation.is_a?(::Arel::Table) && predicate.left.relation.klass == klass &&
110
133
  klass.primary_key == predicate.left.name
@@ -167,19 +190,19 @@ module Switchman
167
190
  end
168
191
 
169
192
  def sharded_foreign_key?(relation, column)
170
- models_for_table(relation.table_name).any? { |m| m.sharded_column?(column) }
193
+ models_for_table(relation.name).any? { |m| m.sharded_column?(column) }
171
194
  end
172
195
 
173
196
  def sharded_primary_key?(relation, column)
174
197
  column = column.to_s
175
- return column == 'id' if relation.klass == ::ActiveRecord::Base
198
+ return column == "id" if relation.klass == ::ActiveRecord::Base
176
199
 
177
200
  relation.klass.primary_key == column && relation.klass.integral_id?
178
201
  end
179
202
 
180
203
  def source_shard_for_foreign_key(relation, column)
181
204
  reflection = nil
182
- models_for_table(relation.table_name).each do |model|
205
+ models_for_table(relation.name).each do |model|
183
206
  reflection = model.send(:reflection_for_integer_attribute, column)
184
207
  break if reflection
185
208
  end
@@ -199,12 +222,11 @@ module Switchman
199
222
 
200
223
  case opts
201
224
  when String, Array
202
- values = Hash === rest.first ? rest.first.values : rest
225
+ values = (Hash === rest.first) ? rest.first.values : rest
203
226
 
204
- values.grep(ActiveRecord::Relation) do |rel|
205
- # serialize subqueries against the same shard as the outer query is currently
206
- # targeted to run against
207
- rel.shard!(primary_shard) if rel.shard_source_value == :implicit && rel.primary_shard != primary_shard
227
+ if shard_source_value != :explicit && values.grep(ActiveRecord::Relation).first
228
+ raise "Sub-queries are not allowed as simple substitutions; " \
229
+ "please build your relation with more structured methods so that Switchman is able to introspect it."
208
230
  end
209
231
 
210
232
  super
@@ -254,9 +276,10 @@ module Switchman
254
276
  send(clause_setter, new_clause)
255
277
  end
256
278
 
257
- def each_transposable_predicate(predicates = nil, &block)
279
+ def each_transposable_predicate(predicates, &block)
258
280
  each_predicate(predicates) do |predicate|
259
- if predicate.is_a?(::Arel::Nodes::Grouping)
281
+ case predicate
282
+ when ::Arel::Nodes::Grouping
260
283
  next predicate unless predicate.expr.is_a?(::Arel::Nodes::Or)
261
284
 
262
285
  or_expr = predicate.expr
@@ -267,6 +290,40 @@ module Switchman
267
290
  next predicate if new_left == old_left && new_right == old_right
268
291
 
269
292
  next predicate.class.new predicate.expr.class.new(new_left, new_right)
293
+ when ::Arel::Nodes::SelectStatement
294
+ new_cores = predicate.cores.map do |core|
295
+ next core unless core.is_a?(::Arel::Nodes::SelectCore) # just in case something weird is going on
296
+
297
+ new_wheres = each_transposable_predicate(core.wheres, &block)
298
+ new_havings = each_transposable_predicate(core.havings, &block)
299
+
300
+ next core if core.wheres == new_wheres && core.havings == new_havings
301
+
302
+ new_core = core.clone
303
+ new_core.wheres = new_wheres
304
+ new_core.havings = new_havings
305
+ new_core
306
+ end
307
+
308
+ next predicate if predicate.cores == new_cores
309
+
310
+ new_node = predicate.clone
311
+ new_node.instance_variable_set(:@cores, new_cores)
312
+ next new_node
313
+ when ::Arel::Nodes::Not
314
+ old_value = predicate.expr
315
+ new_value = each_transposable_predicate([old_value], &block).first
316
+
317
+ next predicate if old_value == new_value
318
+
319
+ next predicate.class.new(new_value)
320
+ when ::Arel::Nodes::Exists
321
+ old_value = predicate.expressions
322
+ new_value = each_transposable_predicate([old_value], &block).first
323
+
324
+ next predicate if old_value == new_value
325
+
326
+ next predicate.class.new(new_value)
270
327
  end
271
328
 
272
329
  next predicate unless predicate.is_a?(::Arel::Nodes::Binary) || predicate.is_a?(::Arel::Nodes::HomogeneousIn)
@@ -279,54 +336,56 @@ module Switchman
279
336
  end
280
337
  end
281
338
 
282
- def each_transposable_predicate_value(predicates = nil)
339
+ def each_transposable_predicate_value(predicates = nil, &block)
283
340
  each_transposable_predicate(predicates) do |predicate, relation, column, type|
284
- each_transposable_predicate_value_cb(predicate) do |value|
341
+ each_transposable_predicate_value_cb(predicate, block) do |value|
285
342
  yield(value, predicate, relation, column, type)
286
343
  end
287
344
  end
288
345
  end
289
346
 
290
- def each_transposable_predicate_value_cb(node, &block)
347
+ def each_transposable_predicate_value_cb(node, original_block, &block)
291
348
  case node
292
349
  when Array
293
- node.map { |val| each_transposable_predicate_value_cb(val, &block).presence }.compact
350
+ node.filter_map { |val| each_transposable_predicate_value_cb(val, original_block, &block).presence }
294
351
  when ::ActiveModel::Attribute
295
352
  old_value = node.value_before_type_cast
296
- new_value = each_transposable_predicate_value_cb(old_value, &block)
353
+ new_value = each_transposable_predicate_value_cb(old_value, original_block, &block)
297
354
 
298
- old_value == new_value ? node : node.class.new(node.name, new_value, node.type)
355
+ (old_value == new_value) ? node : node.class.new(node.name, new_value, node.type)
299
356
  when ::Arel::Nodes::And
300
357
  old_value = node.children
301
- new_value = each_transposable_predicate_value_cb(old_value, &block)
358
+ new_value = each_transposable_predicate_value_cb(old_value, original_block, &block)
302
359
 
303
- old_value == new_value ? node : node.class.new(new_value)
360
+ (old_value == new_value) ? node : node.class.new(new_value)
304
361
  when ::Arel::Nodes::BindParam
305
362
  old_value = node.value
306
- new_value = each_transposable_predicate_value_cb(old_value, &block)
363
+ new_value = each_transposable_predicate_value_cb(old_value, original_block, &block)
307
364
 
308
- old_value == new_value ? node : node.class.new(new_value)
365
+ (old_value == new_value) ? node : node.class.new(new_value)
309
366
  when ::Arel::Nodes::Casted
310
367
  old_value = node.value
311
- new_value = each_transposable_predicate_value_cb(old_value, &block)
368
+ new_value = each_transposable_predicate_value_cb(old_value, original_block, &block)
312
369
 
313
- old_value == new_value ? node : node.class.new(new_value, node.attribute)
370
+ (old_value == new_value) ? node : node.class.new(new_value, node.attribute)
314
371
  when ::Arel::Nodes::HomogeneousIn
315
372
  old_value = node.values
316
- new_value = each_transposable_predicate_value_cb(old_value, &block)
373
+ new_value = each_transposable_predicate_value_cb(old_value, original_block, &block)
317
374
 
318
375
  # switch to a regular In, so that Relation::WhereClause#contradiction? knows about it
319
376
  if new_value.empty?
320
- klass = node.type == :in ? ::Arel::Nodes::In : ::Arel::Nodes::NotIn
377
+ klass = (node.type == :in) ? ::Arel::Nodes::In : ::Arel::Nodes::NotIn
321
378
  klass.new(node.attribute, new_value)
322
379
  else
323
- old_value == new_value ? node : node.class.new(new_value, node.attribute, node.type)
380
+ (old_value == new_value) ? node : node.class.new(new_value, node.attribute, node.type)
324
381
  end
325
382
  when ::Arel::Nodes::Binary
326
383
  old_value = node.right
327
- new_value = each_transposable_predicate_value_cb(old_value, &block)
384
+ new_value = each_transposable_predicate_value_cb(old_value, original_block, &block)
328
385
 
329
- old_value == new_value ? node : node.class.new(node.left, new_value)
386
+ (old_value == new_value) ? node : node.class.new(node.left, new_value)
387
+ when ::Arel::Nodes::SelectStatement
388
+ each_transposable_predicate_value([node], &original_block).first
330
389
  else
331
390
  yield(node)
332
391
  end
@@ -343,6 +402,8 @@ module Switchman
343
402
  Shard.current(klass.connection_class_for_self)
344
403
  elsif type == :foreign
345
404
  source_shard_for_foreign_key(relation, column)
405
+ else
406
+ primary_shard
346
407
  end
347
408
 
348
409
  transpose_predicate_value(value, current_source_shard, target_shard, type)
@@ -350,7 +411,9 @@ module Switchman
350
411
  end
351
412
 
352
413
  def transpose_predicate_value(value, current_shard, target_shard, attribute_type)
353
- if value.is_a?(::ActiveRecord::StatementCache::Substitute)
414
+ if value.is_a?(NonTransposingValue)
415
+ value
416
+ elsif value.is_a?(::ActiveRecord::StatementCache::Substitute)
354
417
  value.sharded = true # mark for transposition later
355
418
  value.primary = true if attribute_type == :primary
356
419
  value
@@ -4,7 +4,7 @@ module Switchman
4
4
  module ActiveRecord
5
5
  module Relation
6
6
  def self.prepended(klass)
7
- klass::SINGLE_VALUE_METHODS.concat %i[shard shard_source]
7
+ klass::SINGLE_VALUE_METHODS.push(:shard, :shard_source)
8
8
  end
9
9
 
10
10
  def initialize(*, **)
@@ -44,12 +44,18 @@ module Switchman
44
44
  primary_shard.activate(klass.connection_class_for_self) { super }
45
45
  end
46
46
 
47
- def explain
48
- activate { |relation| relation.call_super(:explain, Relation) }
47
+ if ::Rails.version > "7.1.2"
48
+ def transaction(...)
49
+ primary_shard.activate(klass.connection_class_for_self) { super }
50
+ end
51
+ end
52
+
53
+ def explain(*args)
54
+ activate { |relation| relation.call_super(:explain, Relation, *args) }
49
55
  end
50
56
 
51
57
  def load(&block)
52
- if !loaded? || (::Rails.version >= '7.0' && scheduled?)
58
+ if !loaded? || scheduled?
53
59
  @records = activate { |relation| relation.send(:exec_queries, &block) }
54
60
  @loaded = true
55
61
  end
@@ -58,10 +64,9 @@ module Switchman
58
64
  end
59
65
 
60
66
  %I[update_all delete_all].each do |method|
61
- arg_params = RUBY_VERSION <= '2.8' ? '*args' : '*args, **kwargs'
62
67
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
63
- def #{method}(#{arg_params})
64
- result = self.activate(unordered: true) { |relation| relation.call_super(#{method.inspect}, Relation, #{arg_params}) }
68
+ def #{method}(*args, **kwargs)
69
+ result = self.activate(unordered: true) { |relation| relation.call_super(#{method.inspect}, Relation, *args, **kwargs) }
65
70
  result = result.sum if result.is_a?(Array)
66
71
  result
67
72
  end
@@ -73,12 +78,16 @@ module Switchman
73
78
  loose_mode = options[:loose] && is_integer
74
79
  # loose_mode: if we don't care about getting exactly batch_size ids in between
75
80
  # don't get the max - just get the min and add batch_size so we get that many _at most_
76
- values = loose_mode ? 'MIN(id)' : 'MIN(id), MAX(id)'
81
+ values = loose_mode ? "MIN(id)" : "MIN(id), MAX(id)"
77
82
 
78
83
  batch_size = options[:batch_size].try(:to_i) || 1000
79
- quoted_primary_key = "#{klass.connection.quote_local_table_name(table_name)}.#{klass.connection.quote_column_name(primary_key)}"
80
- as_id = ' AS id' unless primary_key == 'id'
81
- subquery_scope = except(:select).select("#{quoted_primary_key}#{as_id}").reorder(primary_key.to_sym).limit(loose_mode ? 1 : batch_size)
84
+ quoted_primary_key =
85
+ "#{klass.connection.quote_local_table_name(table_name)}.#{klass.connection.quote_column_name(primary_key)}"
86
+ as_id = " AS id" unless primary_key == "id"
87
+ subquery_scope = except(:select)
88
+ .select("#{quoted_primary_key}#{as_id}")
89
+ .reorder(primary_key.to_sym)
90
+ .limit(loose_mode ? 1 : batch_size)
82
91
  subquery_scope = subquery_scope.where("#{quoted_primary_key} <= ?", options[:end_at]) if options[:end_at]
83
92
 
84
93
  first_subquery_scope = if options[:start_at]
@@ -121,7 +130,9 @@ module Switchman
121
130
  relation = shard(Shard.current(klass.connection_class_for_self))
122
131
  relation.remove_nonlocal_primary_keys!
123
132
  # do a minimal query if possible
124
- relation = relation.limit(limit_value - result_count) if limit_value && !result_count.zero? && order_values.empty?
133
+ if limit_value && !result_count.zero? && order_values.empty?
134
+ relation = relation.limit(limit_value - result_count)
135
+ end
125
136
 
126
137
  shard_results = relation.activate(&block)
127
138
 
@@ -17,7 +17,7 @@ module Switchman
17
17
  final_shard_source_value = %i[explicit association].detect do |source_value|
18
18
  shard_source_value == source_value || rhs.shard_source_value == source_value
19
19
  end
20
- raise 'unknown shard_source_value' unless final_shard_source_value
20
+ raise "unknown shard_source_value" unless final_shard_source_value
21
21
 
22
22
  # have to merge shard_value
23
23
  lhs_shard_value = all_shards
@@ -36,7 +36,7 @@ module Switchman
36
36
  final_shard_source_value = %i[explicit association implicit].detect do |source_value|
37
37
  shard_source_value == source_value || rhs.shard_source_value == source_value
38
38
  end
39
- raise 'unknown shard_source_value' unless final_shard_source_value
39
+ raise "unknown shard_source_value" unless final_shard_source_value
40
40
  end
41
41
 
42
42
  [final_shard_value, final_primary_shard, final_shard_source_value]
@@ -4,8 +4,8 @@ module Switchman
4
4
  module ActiveRecord
5
5
  module StatementCache
6
6
  module ClassMethods
7
- def create(connection, &block)
8
- relation = block.call ::ActiveRecord::StatementCache::Params.new
7
+ def create(connection)
8
+ relation = yield ::ActiveRecord::StatementCache::Params.new
9
9
 
10
10
  _query_builder, binds = connection.cacheable_query(self, relation.arel)
11
11
  bind_map = ::ActiveRecord::StatementCache::BindMap.new(binds)
@@ -7,9 +7,14 @@ module Switchman
7
7
  def drop(*)
8
8
  super
9
9
  # no really, it's gone
10
- Switchman.cache.delete('default_shard')
10
+ Switchman.cache.delete("default_shard")
11
11
  Shard.default(reload: true)
12
12
  end
13
+
14
+ def raise_for_multi_db(*)
15
+ # ignore; Switchman doesn't use namespaced tasks for multiple shards; it uses
16
+ # environment variables to filter which shards you want to target
17
+ end
13
18
  end
14
19
  end
15
20
  end
@@ -12,31 +12,41 @@ module Switchman
12
12
  # Replace the one that activerecord natively uses with a switchman-optimized one
13
13
  ::ActiveSupport::Notifications.unsubscribe(@connection_subscriber)
14
14
  # Code adapted from the code in rails proper
15
- @connection_subscriber = ::ActiveSupport::Notifications.subscribe('!connection.active_record') do |_, _, _, _, payload|
16
- spec_name = payload[:spec_name] if payload.key?(:spec_name)
17
- shard = payload[:shard] if payload.key?(:shard)
18
- setup_shared_connection_pool
15
+ @connection_subscriber =
16
+ ::ActiveSupport::Notifications.subscribe("!connection.active_record") do |_, _, _, _, payload|
17
+ spec_name = if ::Rails.version < "7.1"
18
+ payload[:spec_name] if payload.key?(:spec_name)
19
+ elsif payload.key?(:connection_name)
20
+ payload[:connection_name]
21
+ end
22
+ shard = payload[:shard] if payload.key?(:shard)
19
23
 
20
- if spec_name && !FORBIDDEN_DB_ENVS.include?(shard)
21
- begin
22
- connection = ::ActiveRecord::Base.connection_handler.retrieve_connection(spec_name, shard: shard)
23
- rescue ::ActiveRecord::ConnectionNotEstablished, ::ActiveRecord::NoDatabaseError
24
- connection = nil
25
- end
24
+ if spec_name && !FORBIDDEN_DB_ENVS.include?(shard)
25
+ begin
26
+ connection = ::ActiveRecord::Base.connection_handler.retrieve_connection(spec_name, shard: shard)
27
+ connection.connect! if ::Rails.version >= "7.1" # eagerly validate the connection
28
+ rescue ::ActiveRecord::ConnectionNotEstablished, ::ActiveRecord::NoDatabaseError
29
+ connection = nil
30
+ end
26
31
 
27
- if connection && !@fixture_connections.include?(connection)
28
- connection.begin_transaction joinable: false, _lazy: false
29
- connection.pool.lock_thread = true if lock_threads
30
- @fixture_connections << connection
32
+ if connection
33
+ setup_shared_connection_pool
34
+ unless @fixture_connections.include?(connection)
35
+ connection.begin_transaction joinable: false, _lazy: false
36
+ connection.pool.lock_thread = true if lock_threads
37
+ @fixture_connections << connection
38
+ end
39
+ end
31
40
  end
32
41
  end
33
- end
34
42
  end
35
43
 
36
44
  def enlist_fixture_connections
37
45
  setup_shared_connection_pool
38
46
 
39
- ::ActiveRecord::Base.connection_handler.connection_pool_list.reject { |cp| FORBIDDEN_DB_ENVS.include?(cp.db_config.env_name.to_sym) }.map(&:connection)
47
+ ::ActiveRecord::Base.connection_handler.connection_pool_list(:primary).reject do |cp|
48
+ FORBIDDEN_DB_ENVS.include?(cp.db_config.env_name.to_sym)
49
+ end.map(&:connection)
40
50
  end
41
51
  end
42
52
  end
@@ -22,9 +22,14 @@ module Switchman
22
22
 
23
23
  def lookup_store(*store_options)
24
24
  store = super
25
- # can't use defined?, because it's a _ruby_ autoloaded constant,
26
- # so just checking that will cause it to get required
27
- ::ActiveSupport::Cache::RedisCacheStore.prepend(RedisCacheStore) if store.instance_of?(ActiveSupport::Cache::RedisCacheStore) && !::ActiveSupport::Cache::RedisCacheStore.ancestors.include?(RedisCacheStore)
25
+ # must use the string name, otherwise it will try to auto-load the constant
26
+ # and we don't want to require redis in this file (since it's not a hard dependency)
27
+ # rubocop:disable Style/ClassEqualityComparison
28
+ if store.class.name == "ActiveSupport::Cache::RedisCacheStore" &&
29
+ !(::ActiveSupport::Cache::RedisCacheStore <= RedisCacheStore)
30
+ ::ActiveSupport::Cache::RedisCacheStore.prepend(RedisCacheStore)
31
+ end
32
+ # rubocop:enable Style/ClassEqualityComparison
28
33
  store.options[:namespace] ||= -> { Shard.current.default? ? nil : "shard_#{Shard.current.id}" }
29
34
  store
30
35
  end
@@ -33,7 +38,7 @@ module Switchman
33
38
  module RedisCacheStore
34
39
  def clear(namespace: nil, **)
35
40
  # RedisCacheStore tries to be smart and only clear the cache under your namespace, if you have one set
36
- # unfortunately, it uses the keys command, which is extraordinarily inefficient in a large redis instance
41
+ # unfortunately, it doesn't work using redis clustering because of the way redis keys are distributed
37
42
  # fortunately, we can assume we control the entire instance, because we set up the namespacing, so just
38
43
  # always unset it temporarily for clear calls
39
44
  namespace = nil # rubocop:disable Lint/ShadowedArgument
@@ -13,38 +13,54 @@ module Switchman
13
13
  # rubocop:disable Naming/MethodName
14
14
  # rubocop:disable Naming/MethodParameterName
15
15
 
16
+ def visit_Arel_Nodes_Cte(o, collector)
17
+ collector << quote_local_table_name(o.name)
18
+ collector << " AS "
19
+
20
+ case o.materialized
21
+ when true
22
+ collector << "MATERIALIZED "
23
+ when false
24
+ collector << "NOT MATERIALIZED "
25
+ end
26
+
27
+ visit o.relation, collector
28
+ end
29
+
16
30
  def visit_Arel_Nodes_TableAlias(o, collector)
17
31
  collector = visit o.relation, collector
18
- collector << ' '
32
+ collector << " "
19
33
  collector << quote_local_table_name(o.name)
20
34
  end
21
35
 
22
36
  def visit_Arel_Attributes_Attribute(o, collector)
23
37
  join_name = o.relation.table_alias || o.relation.name
24
- collector << quote_local_table_name(join_name) << '.' << quote_column_name(o.name)
38
+ collector << quote_local_table_name(join_name) << "." << quote_column_name(o.name)
25
39
  end
26
40
 
27
- def visit_Arel_Nodes_HomogeneousIn(o, collector)
28
- collector.preparable = false
41
+ if ::Rails.version < "7.1"
42
+ def visit_Arel_Nodes_HomogeneousIn(o, collector)
43
+ collector.preparable = false
29
44
 
30
- collector << quote_local_table_name(o.table_name) << '.' << quote_column_name(o.column_name)
45
+ collector << quote_local_table_name(o.table_name) << "." << quote_column_name(o.column_name)
31
46
 
32
- collector << if o.type == :in
33
- ' IN ('
34
- else
35
- ' NOT IN ('
36
- end
47
+ collector << if o.type == :in
48
+ " IN ("
49
+ else
50
+ " NOT IN ("
51
+ end
37
52
 
38
- values = o.casted_values
53
+ values = o.casted_values
39
54
 
40
- if values.empty?
41
- collector << @connection.quote(nil)
42
- else
43
- collector.add_binds(values, o.proc_for_binds, &bind_block)
44
- end
55
+ if values.empty?
56
+ collector << @connection.quote(nil)
57
+ else
58
+ collector.add_binds(values, o.proc_for_binds, &bind_block)
59
+ end
45
60
 
46
- collector << ')'
47
- collector
61
+ collector << ")"
62
+ collector
63
+ end
48
64
  end
49
65
 
50
66
  # rubocop:enable Naming/MethodName
@@ -12,14 +12,8 @@ module Switchman
12
12
  method.super_method
13
13
  end
14
14
 
15
- if RUBY_VERSION <= '2.8'
16
- def call_super(method, above_module, *args, &block)
17
- super_method_above(method, above_module).call(*args, &block)
18
- end
19
- else
20
- def call_super(method, above_module, *args, **kwargs, &block)
21
- super_method_above(method, above_module).call(*args, **kwargs, &block)
22
- end
15
+ def call_super(method, above_module, ...)
16
+ super_method_above(method, above_module).call(...)
23
17
  end
24
18
  end
25
19
  end