switchman 4.1.1 → 4.2.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.
- checksums.yaml +4 -4
- data/lib/switchman/active_record/abstract_adapter.rb +0 -6
- data/lib/switchman/active_record/associations.rb +42 -58
- data/lib/switchman/active_record/attribute_methods.rb +3 -3
- data/lib/switchman/active_record/base.rb +2 -6
- data/lib/switchman/active_record/calculations.rb +31 -42
- data/lib/switchman/active_record/connection_pool.rb +5 -20
- data/lib/switchman/active_record/database_configurations.rb +7 -19
- data/lib/switchman/active_record/finder_methods.rb +19 -43
- data/lib/switchman/active_record/log_subscriber.rb +1 -10
- data/lib/switchman/active_record/migration.rb +6 -22
- data/lib/switchman/active_record/query_cache.rb +27 -55
- data/lib/switchman/active_record/statement_cache.rb +2 -2
- data/lib/switchman/active_record/test_fixtures.rb +2 -6
- data/lib/switchman/arel.rb +0 -25
- data/lib/switchman/database_server.rb +1 -5
- data/lib/switchman/engine.rb +1 -5
- data/lib/switchman/shard.rb +1 -5
- data/lib/switchman/version.rb +1 -1
- metadata +14 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cfb4e89f881628a5d90c8f309ddf587fdc5bfebe5bc7985c79fea9dd06e7168c
|
4
|
+
data.tar.gz: baa3202cd9fb68347d71d43adecdbe3124fe3f4f604558f92546e8fa5c5b4773
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c351d3ff898e668ac7a8c8ab40e9cf2249f428a2d2bbc9a16573334e1599cb9bcac20a47a3534e70b7900fd76ca55f6f4a8625c9dc7c8bb3ef1b4ade95a17ff3
|
7
|
+
data.tar.gz: e3205573c3bbf8d8bf8a1e87beaaaed1adc2dba268f53ec1bac8c870f29f51ecdce923028eaa8528cffb9250965fc7b72d8d2dce4882c63edb0ad0d66d624a57
|
@@ -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(*
|
255
|
-
scope.shard(*
|
256
|
+
def shard(*)
|
257
|
+
scope.shard(*)
|
256
258
|
end
|
257
259
|
end
|
258
260
|
|
259
261
|
module AutosaveAssociation
|
260
|
-
|
261
|
-
|
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
|
-
|
265
|
-
|
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
|
-
|
279
|
-
|
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
|
-
|
282
|
-
|
283
|
-
|
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
|
-
|
286
|
-
|
287
|
-
return unless association&.loaded? && !association.stale_target?
|
276
|
+
record = association.load_target
|
277
|
+
return unless record && !record.destroyed?
|
288
278
|
|
289
|
-
|
290
|
-
return unless record && !record.destroyed?
|
279
|
+
autosave = reflection.options[:autosave]
|
291
280
|
|
292
|
-
|
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
|
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
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
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:, &
|
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, &
|
62
|
+
owner.define_cached_method(as, namespace:, as: name, &)
|
63
63
|
else
|
64
|
-
owner.define_cached_method(name, namespace:, as:, &
|
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 =
|
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|
|
@@ -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 &&
|
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
|
-
|
110
|
-
|
111
|
-
return super unless klass.sharded_primary_key?
|
109
|
+
def ids
|
110
|
+
return super unless klass.sharded_primary_key?
|
112
111
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
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
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
121
|
+
if has_include?(primary_key)
|
122
|
+
relation = apply_join_dependency.group(primary_key)
|
123
|
+
return relation.ids
|
124
|
+
end
|
126
125
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
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
|
-
|
142
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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
|
-
|
55
|
+
return false if !conditions || limit_value == 0 # rubocop:disable Style/NumericPredicate
|
79
56
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
57
|
+
if eager_loading?
|
58
|
+
relation = apply_join_dependency(eager_loading: false)
|
59
|
+
return relation.exists?(conditions)
|
60
|
+
end
|
84
61
|
|
85
|
-
|
86
|
-
|
62
|
+
relation = construct_relation_for_exists(conditions)
|
63
|
+
return false if relation.where_clause.contradiction?
|
87
64
|
|
88
|
-
|
89
|
-
|
90
|
-
|
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 =
|
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,11 +29,7 @@ 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
|
-
|
33
|
-
::ActiveRecord::InternalMetadata[:migrator_advisory_lock_id] = shard_name_hash.to_s
|
34
|
-
else
|
35
|
-
@internal_metadata[:migrator_advisory_lock_id] = shard_name_hash.to_s
|
36
|
-
end
|
32
|
+
@internal_metadata[:migrator_advisory_lock_id] = shard_name_hash.to_s
|
37
33
|
shard_name_hash
|
38
34
|
end
|
39
35
|
|
@@ -51,26 +47,14 @@ module Switchman
|
|
51
47
|
|
52
48
|
module MigrationContext
|
53
49
|
def migrate(...)
|
54
|
-
connection = ::ActiveRecord::Base.connection
|
55
50
|
schema_cache_holder = ::ActiveRecord::Base.connection_pool
|
56
|
-
schema_cache_holder = schema_cache_holder.schema_reflection
|
57
|
-
previous_schema_cache =
|
58
|
-
schema_cache_holder.get_schema_cache(connection)
|
59
|
-
else
|
60
|
-
schema_cache_holder.instance_variable_get(:@cache)
|
61
|
-
end
|
62
|
-
|
63
|
-
if ::Rails.version < "7.1"
|
64
|
-
temporary_schema_cache = ::ActiveRecord::ConnectionAdapters::SchemaCache.new(connection)
|
51
|
+
schema_cache_holder = schema_cache_holder.schema_reflection
|
52
|
+
previous_schema_cache = schema_cache_holder.instance_variable_get(:@cache)
|
65
53
|
|
66
|
-
|
67
|
-
schema_cache_holder.set_schema_cache(temporary_schema_cache)
|
68
|
-
else
|
69
|
-
schema_cache_holder.instance_variable_get(:@cache)
|
54
|
+
schema_cache_holder.instance_variable_get(:@cache)
|
70
55
|
|
71
|
-
|
72
|
-
|
73
|
-
end
|
56
|
+
reset_column_information
|
57
|
+
schema_cache_holder.clear!
|
74
58
|
|
75
59
|
begin
|
76
60
|
super
|
@@ -5,67 +5,39 @@ module Switchman
|
|
5
5
|
module QueryCache
|
6
6
|
private
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
-
|
43
|
-
|
44
|
-
|
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
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
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
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
@@ -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
|
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
23
|
connection = ::ActiveRecord::Base.connection_handler.retrieve_connection(spec_name, shard: shard)
|
28
|
-
connection.connect!
|
24
|
+
connection.connect! # eagerly validate the connection
|
29
25
|
rescue ::ActiveRecord::ConnectionNotEstablished
|
30
26
|
connection = nil
|
31
27
|
end
|
data/lib/switchman/arel.rb
CHANGED
@@ -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
|
-
|
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|
|
data/lib/switchman/engine.rb
CHANGED
@@ -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
|
-
|
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)
|
data/lib/switchman/shard.rb
CHANGED
@@ -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
|
-
|
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|
|
data/lib/switchman/version.rb
CHANGED
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.
|
4
|
+
version: 4.2.0
|
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-
|
13
|
+
date: 2025-08-25 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activerecord
|
@@ -18,34 +18,34 @@ dependencies:
|
|
18
18
|
requirements:
|
19
19
|
- - ">="
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version: '7.
|
21
|
+
version: '7.1'
|
22
22
|
- - "<"
|
23
23
|
- !ruby/object:Gem::Version
|
24
|
-
version: '
|
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.
|
31
|
+
version: '7.1'
|
32
32
|
- - "<"
|
33
33
|
- !ruby/object:Gem::Version
|
34
|
-
version: '
|
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
|
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
|
48
|
+
version: 3.1.0
|
49
49
|
- !ruby/object:Gem::Dependency
|
50
50
|
name: parallel
|
51
51
|
requirement: !ruby/object:Gem::Requirement
|
@@ -66,20 +66,20 @@ dependencies:
|
|
66
66
|
requirements:
|
67
67
|
- - ">="
|
68
68
|
- !ruby/object:Gem::Version
|
69
|
-
version: '7.
|
69
|
+
version: '7.1'
|
70
70
|
- - "<"
|
71
71
|
- !ruby/object:Gem::Version
|
72
|
-
version: '
|
72
|
+
version: '8.1'
|
73
73
|
type: :runtime
|
74
74
|
prerelease: false
|
75
75
|
version_requirements: !ruby/object:Gem::Requirement
|
76
76
|
requirements:
|
77
77
|
- - ">="
|
78
78
|
- !ruby/object:Gem::Version
|
79
|
-
version: '7.
|
79
|
+
version: '7.1'
|
80
80
|
- - "<"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: '
|
82
|
+
version: '8.1'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: debug
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -312,14 +312,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
312
312
|
requirements:
|
313
313
|
- - ">="
|
314
314
|
- !ruby/object:Gem::Version
|
315
|
-
version: '3.
|
315
|
+
version: '3.2'
|
316
316
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
317
317
|
requirements:
|
318
318
|
- - ">="
|
319
319
|
- !ruby/object:Gem::Version
|
320
320
|
version: '0'
|
321
321
|
requirements: []
|
322
|
-
rubygems_version: 3.
|
322
|
+
rubygems_version: 3.4.19
|
323
323
|
signing_key:
|
324
324
|
specification_version: 4
|
325
325
|
summary: Rails sharding magic
|