switchman 3.0.2 → 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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +16 -15
  3. data/db/migrate/20180828183945_add_default_shard_index.rb +2 -2
  4. data/db/migrate/20180828192111_add_timestamps_to_shards.rb +1 -1
  5. data/db/migrate/20190114212900_add_unique_name_indexes.rb +10 -4
  6. data/lib/switchman/action_controller/caching.rb +2 -2
  7. data/lib/switchman/active_record/abstract_adapter.rb +11 -18
  8. data/lib/switchman/active_record/associations.rb +315 -0
  9. data/lib/switchman/active_record/attribute_methods.rb +191 -79
  10. data/lib/switchman/active_record/base.rb +204 -50
  11. data/lib/switchman/active_record/calculations.rb +92 -49
  12. data/lib/switchman/active_record/connection_handler.rb +18 -0
  13. data/lib/switchman/active_record/connection_pool.rb +47 -34
  14. data/lib/switchman/active_record/database_configurations.rb +32 -6
  15. data/lib/switchman/active_record/finder_methods.rb +22 -16
  16. data/lib/switchman/active_record/log_subscriber.rb +3 -6
  17. data/lib/switchman/active_record/migration.rb +42 -14
  18. data/lib/switchman/active_record/model_schema.rb +1 -1
  19. data/lib/switchman/active_record/pending_migration_connection.rb +17 -0
  20. data/lib/switchman/active_record/persistence.rb +37 -2
  21. data/lib/switchman/active_record/postgresql_adapter.rb +39 -20
  22. data/lib/switchman/active_record/predicate_builder.rb +2 -2
  23. data/lib/switchman/active_record/query_cache.rb +26 -17
  24. data/lib/switchman/active_record/query_methods.rb +251 -140
  25. data/lib/switchman/active_record/reflection.rb +10 -3
  26. data/lib/switchman/active_record/relation.rb +110 -35
  27. data/lib/switchman/active_record/spawn_methods.rb +3 -7
  28. data/lib/switchman/active_record/statement_cache.rb +13 -9
  29. data/lib/switchman/active_record/table_definition.rb +1 -1
  30. data/lib/switchman/active_record/tasks/database_tasks.rb +6 -1
  31. data/lib/switchman/active_record/test_fixtures.rb +89 -0
  32. data/lib/switchman/active_support/cache.rb +25 -4
  33. data/lib/switchman/arel.rb +20 -7
  34. data/lib/switchman/call_super.rb +2 -2
  35. data/lib/switchman/database_server.rb +123 -83
  36. data/lib/switchman/default_shard.rb +14 -5
  37. data/lib/switchman/engine.rb +85 -131
  38. data/lib/switchman/environment.rb +2 -2
  39. data/lib/switchman/errors.rb +17 -2
  40. data/lib/switchman/guard_rail/relation.rb +7 -10
  41. data/lib/switchman/guard_rail.rb +5 -0
  42. data/lib/switchman/parallel.rb +68 -0
  43. data/lib/switchman/r_spec_helper.rb +17 -28
  44. data/lib/switchman/rails.rb +1 -4
  45. data/{app/models → lib}/switchman/shard.rb +229 -246
  46. data/lib/switchman/sharded_instrumenter.rb +9 -3
  47. data/lib/switchman/shared_schema_cache.rb +11 -0
  48. data/lib/switchman/standard_error.rb +15 -12
  49. data/lib/switchman/test_helper.rb +3 -3
  50. data/{app/models → lib}/switchman/unsharded_record.rb +1 -1
  51. data/lib/switchman/version.rb +1 -1
  52. data/lib/switchman.rb +44 -12
  53. data/lib/tasks/switchman.rake +101 -54
  54. metadata +34 -176
  55. data/lib/switchman/active_record/association.rb +0 -206
  56. data/lib/switchman/open4.rb +0 -80
@@ -4,18 +4,18 @@ 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(*, **)
11
11
  super
12
- self.shard_value = Shard.current(klass ? klass.connection_classes : :primary) unless shard_value
12
+ self.shard_value = Shard.current(klass ? klass.connection_class_for_self : :primary) unless shard_value
13
13
  self.shard_source_value = :implicit unless shard_source_value
14
14
  end
15
15
 
16
16
  def clone
17
17
  result = super
18
- result.shard_value = Shard.current(klass ? klass.connection_classes : :primary) unless shard_value
18
+ result.shard_value = Shard.current(klass ? klass.connection_class_for_self : :primary) unless shard_value
19
19
  result
20
20
  end
21
21
 
@@ -28,59 +28,119 @@ module Switchman
28
28
  relation
29
29
  end
30
30
 
31
- def new(*, &block)
32
- primary_shard.activate(klass.connection_classes) { super }
31
+ def new(*, &)
32
+ primary_shard.activate(klass.connection_class_for_self) { super }
33
33
  end
34
34
 
35
- def create(*, &block)
36
- primary_shard.activate(klass.connection_classes) { super }
35
+ def create(*, &)
36
+ primary_shard.activate(klass.connection_class_for_self) { super }
37
37
  end
38
38
 
39
- def create!(*, &block)
40
- primary_shard.activate(klass.connection_classes) { super }
39
+ def create!(*, &)
40
+ primary_shard.activate(klass.connection_class_for_self) { super }
41
41
  end
42
42
 
43
43
  def to_sql
44
- primary_shard.activate(klass.connection_classes) { super }
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
49
51
  end
50
52
 
51
- def records
52
- return @records if loaded?
53
+ def explain(*args)
54
+ activate { |relation| relation.call_super(:explain, Relation, *args) }
55
+ end
53
56
 
54
- results = activate { |relation| relation.call_super(:records, Relation) }
55
- case shard_value
56
- when Array, ::ActiveRecord::Relation, ::ActiveRecord::Base
57
- @records = results
57
+ def load(&block)
58
+ if !loaded? || scheduled?
59
+ @records = activate { |relation| relation.send(:exec_queries, &block) }
58
60
  @loaded = true
59
61
  end
60
- results
62
+
63
+ self
61
64
  end
62
65
 
63
66
  %I[update_all delete_all].each do |method|
64
67
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
65
- def #{method}(*args)
66
- result = self.activate { |relation| relation.call_super(#{method.inspect}, Relation, *args) }
68
+ def #{method}(*args, **kwargs)
69
+ result = self.activate(unordered: true) { |relation| relation.call_super(#{method.inspect}, Relation, *args, **kwargs) }
67
70
  result = result.sum if result.is_a?(Array)
68
71
  result
69
72
  end
70
73
  RUBY
71
74
  end
72
75
 
76
+ # https://github.com/rails/rails/commit/ed2c15b52450ff927a05629f031376f25b670335
77
+ # once the minimum version is Rails 7.2, we can drop this separate module
78
+ module InsertUpsertAll
79
+ %w[insert_all upsert_all].each do |method|
80
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
81
+ def #{method}(attributes, returning: nil, **)
82
+ scope = self != ::ActiveRecord::Base && current_scope
83
+ if (target_shard = scope&.primary_shard) == (current_shard = Shard.current(connection_class_for_self))
84
+ scope = nil
85
+ end
86
+ if scope
87
+ dupped = false
88
+ attributes.each_with_index do |hash, i|
89
+ if dupped || hash.any? { |k, v| sharded_column?(k) }
90
+ unless dupped
91
+ attributes = attributes.dup
92
+ dupped = true
93
+ end
94
+ attributes[i] = hash.to_h do |k, v|
95
+ if sharded_column?(k)
96
+ [k, Shard.relative_id_for(v, current_shard, target_shard)]
97
+ else
98
+ [k, v]
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+
105
+ if scope
106
+ scope.activate do
107
+ db = Shard.current(connection_class_for_self).database_server
108
+ result = db.unguard { super }
109
+ if result&.columns&.any? { |c| sharded_column?(c) }
110
+ transposed_rows = result.rows.map do |row|
111
+ row.map.with_index do |value, i|
112
+ sharded_column?(result.columns[i]) ? Shard.relative_id_for(value, target_shard, current_shard) : value
113
+ end
114
+ end
115
+ result = ::ActiveRecord::Result.new(result.columns, transposed_rows, result.column_types)
116
+ end
117
+
118
+ result
119
+ end
120
+ else
121
+ db = Shard.current(connection_class_for_self).database_server
122
+ db.unguard { super }
123
+ end
124
+ end
125
+ RUBY
126
+ end
127
+ end
128
+
73
129
  def find_ids_in_ranges(options = {})
74
130
  is_integer = columns_hash[primary_key.to_s].type == :integer
75
131
  loose_mode = options[:loose] && is_integer
76
132
  # loose_mode: if we don't care about getting exactly batch_size ids in between
77
133
  # don't get the max - just get the min and add batch_size so we get that many _at most_
78
- values = loose_mode ? 'MIN(id)' : 'MIN(id), MAX(id)'
134
+ values = loose_mode ? "MIN(id)" : "MIN(id), MAX(id)"
79
135
 
80
136
  batch_size = options[:batch_size].try(:to_i) || 1000
81
- quoted_primary_key = "#{klass.connection.quote_local_table_name(table_name)}.#{klass.connection.quote_column_name(primary_key)}"
82
- as_id = ' AS id' unless primary_key == 'id'
83
- subquery_scope = except(:select).select("#{quoted_primary_key}#{as_id}").reorder(primary_key.to_sym).limit(loose_mode ? 1 : batch_size)
137
+ quoted_primary_key =
138
+ "#{klass.connection.quote_local_table_name(table_name)}.#{klass.connection.quote_column_name(primary_key)}"
139
+ as_id = " AS id" unless primary_key == "id"
140
+ subquery_scope = except(:select)
141
+ .select("#{quoted_primary_key}#{as_id}")
142
+ .reorder(primary_key.to_sym)
143
+ .limit(loose_mode ? 1 : batch_size)
84
144
  subquery_scope = subquery_scope.where("#{quoted_primary_key} <= ?", options[:end_at]) if options[:end_at]
85
145
 
86
146
  first_subquery_scope = if options[:start_at]
@@ -94,7 +154,7 @@ module Switchman
94
154
 
95
155
  while ids.first.present?
96
156
  ids.map!(&:to_i) if is_integer
97
- ids << ids.first + batch_size if loose_mode
157
+ ids << (ids.first + batch_size) if loose_mode
98
158
 
99
159
  yield(*ids)
100
160
  last_value = ids.last
@@ -103,28 +163,39 @@ module Switchman
103
163
  end
104
164
  end
105
165
 
106
- def activate(&block)
166
+ def activate(count: false, unordered: false, &block)
107
167
  shards = all_shards
108
168
  if Array === shards && shards.length == 1
109
- if shards.first == DefaultShard || shards.first == Shard.current(klass.connection_classes)
169
+ if !loaded? && shard_value != shards.first
170
+ shard(shards.first).activate(&block)
171
+ elsif shards.first == DefaultShard || shards.first == Shard.current(klass.connection_class_for_self)
110
172
  yield(self, shards.first)
111
173
  else
112
- shards.first.activate(klass.connection_classes) { yield(self, shards.first) }
174
+ shards.first.activate(klass.connection_class_for_self) { yield(self, shards.first) }
113
175
  end
114
176
  else
115
177
  result_count = 0
116
178
  can_order = false
117
- result = Shard.with_each_shard(shards, [klass.connection_classes]) do
179
+ result = Shard.with_each_shard(shards, [klass.connection_class_for_self]) do
118
180
  # don't even query other shards if we're already past the limit
119
181
  next if limit_value && result_count >= limit_value && order_values.empty?
120
182
 
121
- relation = shard(Shard.current(klass.connection_classes), :to_a)
183
+ relation = shard(Shard.current(klass.connection_class_for_self))
184
+ relation.remove_nonlocal_primary_keys!
122
185
  # do a minimal query if possible
123
- relation = relation.limit(limit_value - result_count) if limit_value && !result_count.zero? && order_values.empty?
186
+ if limit_value && !result_count.zero? && order_values.empty?
187
+ relation = relation.limit(limit_value - result_count)
188
+ end
124
189
 
125
190
  shard_results = relation.activate(&block)
126
191
 
127
- if shard_results.present?
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
128
199
  can_order ||= can_order_cross_shard_results? unless order_values.empty?
129
200
  raise OrderOnMultiShardQuery if !can_order && !order_values.empty? && result_count.positive?
130
201
 
@@ -148,8 +219,12 @@ module Switchman
148
219
  results.sort! do |l, r|
149
220
  result = 0
150
221
  order_values.each do |ov|
151
- a = l.attribute(ov.expr.name)
152
- b = r.attribute(ov.expr.name)
222
+ if l.is_a?(::ActiveRecord::Base)
223
+ a = l.attribute(ov.expr.name)
224
+ b = r.attribute(ov.expr.name)
225
+ else
226
+ a, b = l, r
227
+ end
153
228
  next if a == b
154
229
 
155
230
  if a.nil? || b.nil?
@@ -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]
@@ -62,16 +62,12 @@ module Switchman
62
62
  if primary_shard != final_primary_shard && rhs.primary_shard != final_primary_shard
63
63
  shard!(final_primary_shard)
64
64
  rhs = rhs.shard(final_primary_shard)
65
- super(rhs)
66
65
  elsif primary_shard != final_primary_shard
67
66
  shard!(final_primary_shard)
68
- super(rhs)
69
67
  elsif rhs.primary_shard != final_primary_shard
70
68
  rhs = rhs.shard(final_primary_shard)
71
- super(rhs)
72
- else
73
- super
74
69
  end
70
+ super
75
71
 
76
72
  self.shard_value = final_shard_value
77
73
  self.shard_source_value = 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)
@@ -25,22 +25,22 @@ module Switchman
25
25
  # we can make some assumptions about the shard source
26
26
  # (e.g. infer from the primary key or use the current shard)
27
27
 
28
- def execute(*args)
28
+ def execute(*args, &block)
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
- current_shard = Shard.current(klass.connection_classes)
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
- target_shard.activate(klass.connection_classes) do
41
+ target_shard.activate(klass.connection_class_for_self) do
42
42
  sql = qualified_query_builder(target_shard, klass).sql_for(bind_values, connection)
43
- klass.find_by_sql(sql, bind_values)
43
+ klass.find_by_sql(sql, bind_values, &block)
44
44
  end
45
45
  end
46
46
 
@@ -66,7 +66,11 @@ module Switchman
66
66
 
67
67
  def primary_value_index
68
68
  primary_ba_index = @bound_attributes.index do |ba|
69
- ba.is_a?(::ActiveRecord::Relation::QueryAttribute) && ba.value.primary
69
+ if ba.value.is_a?(::ActiveRecord::StatementCache::Substitute)
70
+ ba.is_a?(::ActiveRecord::Relation::QueryAttribute) && ba.value.primary
71
+ else
72
+ false
73
+ end
70
74
  end
71
75
  @indexes.index(primary_ba_index) if primary_ba_index
72
76
  end
@@ -4,7 +4,7 @@ module Switchman
4
4
  module ActiveRecord
5
5
  module TableDefinition
6
6
  def column(name, type, limit: nil, **)
7
- Engine.foreign_key_check(name, type, limit: limit)
7
+ Switchman.foreign_key_check(name, type, limit:)
8
8
  super
9
9
  end
10
10
  end
@@ -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
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Switchman
4
+ module ActiveRecord
5
+ module TestFixtures
6
+ FORBIDDEN_DB_ENVS = %i[development production].freeze
7
+
8
+ if ::Rails.version < "7.2"
9
+ def setup_fixtures(config = ::ActiveRecord::Base)
10
+ super
11
+ return unless run_in_transaction?
12
+
13
+ # Replace the one that activerecord natively uses with a switchman-optimized one
14
+ ::ActiveSupport::Notifications.unsubscribe(@connection_subscriber)
15
+ # Code adapted from the code in rails proper
16
+ @connection_subscriber =
17
+ ::ActiveSupport::Notifications.subscribe("!connection.active_record") do |_, _, _, _, payload|
18
+ spec_name = (payload[:connection_name] if payload.key?(:connection_name))
19
+ shard = payload[:shard] if payload.key?(:shard)
20
+
21
+ if spec_name && !FORBIDDEN_DB_ENVS.include?(shard)
22
+ begin
23
+ connection = ::ActiveRecord::Base.connection_handler.retrieve_connection(spec_name, shard:)
24
+ connection.connect! # eagerly validate the connection
25
+ rescue ::ActiveRecord::ConnectionNotEstablished
26
+ connection = nil
27
+ end
28
+
29
+ if connection
30
+ setup_shared_connection_pool
31
+ unless @fixture_connections.include?(connection)
32
+ connection.begin_transaction joinable: false, _lazy: false
33
+ connection.pool.lock_thread = true if lock_threads
34
+ @fixture_connections << connection
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ def enlist_fixture_connections
42
+ setup_shared_connection_pool
43
+
44
+ ::ActiveRecord::Base.connection_handler.connection_pool_list(:primary).reject do |cp|
45
+ FORBIDDEN_DB_ENVS.include?(cp.db_config.env_name.to_sym)
46
+ end.map(&:connection)
47
+ end
48
+ else
49
+ def setup_transactional_fixtures
50
+ setup_shared_connection_pool
51
+
52
+ # Begin transactions for connections already established
53
+ # INST: :writing -> :primary
54
+ @fixture_connection_pools = ::ActiveRecord::Base.connection_handler.connection_pool_list(:primary)
55
+ # INST: filter by FORBIDDEN_DB_ENVS
56
+ @fixture_connection_pools = @fixture_connection_pools.reject do |cp|
57
+ FORBIDDEN_DB_ENVS.include?(cp.db_config.env_name.to_sym)
58
+ end
59
+
60
+ @fixture_connection_pools.each do |pool|
61
+ pool.pin_connection!(lock_threads)
62
+ pool.lease_connection
63
+ end
64
+
65
+ # When connections are established in the future, begin a transaction too
66
+ @connection_subscriber = ::ActiveSupport::Notifications
67
+ .subscribe("!connection.active_record") do |_, _, _, _, payload|
68
+ connection_name = payload[:connection_name] if payload.key?(:connection_name)
69
+ shard = payload[:shard] if payload.key?(:shard)
70
+
71
+ # INST: filter by FORBIDDEN_DB_ENVS
72
+ if connection_name && !FORBIDDEN_DB_ENVS.include?(shard)
73
+ pool = ::ActiveRecord::Base.connection_handler.retrieve_connection_pool(connection_name, shard:)
74
+ if pool
75
+ setup_shared_connection_pool
76
+
77
+ unless @fixture_connection_pools.include?(pool)
78
+ pool.pin_connection!(lock_threads)
79
+ pool.lease_connection
80
+ @fixture_connection_pools << pool
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -4,11 +4,32 @@ module Switchman
4
4
  module ActiveSupport
5
5
  module Cache
6
6
  module ClassMethods
7
+ def lookup_stores(cache_store_config)
8
+ result = {}
9
+ cache_store_config.each do |key, value|
10
+ next if value.is_a?(String)
11
+
12
+ result[key] = ::ActiveSupport::Cache.lookup_store(value)
13
+ end
14
+
15
+ cache_store_config.each do |key, value| # rubocop:disable Style/CombinableLoops
16
+ next unless value.is_a?(String)
17
+
18
+ result[key] = result[value]
19
+ end
20
+ result
21
+ end
22
+
7
23
  def lookup_store(*store_options)
8
24
  store = super
9
- # can't use defined?, because it's a _ruby_ autoloaded constant,
10
- # so just checking that will cause it to get required
11
- ::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
12
33
  store.options[:namespace] ||= -> { Shard.current.default? ? nil : "shard_#{Shard.current.id}" }
13
34
  store
14
35
  end
@@ -17,7 +38,7 @@ module Switchman
17
38
  module RedisCacheStore
18
39
  def clear(namespace: nil, **)
19
40
  # RedisCacheStore tries to be smart and only clear the cache under your namespace, if you have one set
20
- # 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
21
42
  # fortunately, we can assume we control the entire instance, because we set up the namespacing, so just
22
43
  # always unset it temporarily for clear calls
23
44
  namespace = nil # rubocop:disable Lint/ShadowedArgument
@@ -11,22 +11,35 @@ module Switchman
11
11
  module Visitors
12
12
  module ToSql
13
13
  # rubocop:disable Naming/MethodName
14
+ # rubocop:disable Naming/MethodParameterName
14
15
 
15
- def visit_Arel_Nodes_TableAlias(*args)
16
- o, collector = args
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
+
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
- def visit_Arel_Attributes_Attribute(*args)
23
- o = args.first
36
+ def visit_Arel_Attributes_Attribute(o, collector)
24
37
  join_name = o.relation.table_alias || o.relation.name
25
- result = "#{quote_local_table_name join_name}.#{quote_column_name o.name}"
26
- args.last << result
38
+ collector << quote_local_table_name(join_name) << "." << quote_column_name(o.name)
27
39
  end
28
40
 
29
41
  # rubocop:enable Naming/MethodName
42
+ # rubocop:enable Naming/MethodParameterName
30
43
 
31
44
  def quote_local_table_name(name)
32
45
  return name if ::Arel::Nodes::SqlLiteral === name
@@ -12,8 +12,8 @@ module Switchman
12
12
  method.super_method
13
13
  end
14
14
 
15
- def call_super(method, above_module, *args, &block)
16
- super_method_above(method, above_module).call(*args, &block)
15
+ def call_super(method, above_module, ...)
16
+ super_method_above(method, above_module).call(...)
17
17
  end
18
18
  end
19
19
  end