switchman 3.0.4 → 3.0.7
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/app/models/switchman/shard.rb +108 -134
- data/db/migrate/20180828183945_add_default_shard_index.rb +1 -1
- data/db/migrate/20180828192111_add_timestamps_to_shards.rb +1 -1
- data/lib/switchman/active_record/abstract_adapter.rb +0 -9
- data/lib/switchman/active_record/attribute_methods.rb +24 -3
- data/lib/switchman/active_record/base.rb +38 -21
- data/lib/switchman/active_record/calculations.rb +1 -1
- data/lib/switchman/active_record/connection_pool.rb +9 -29
- data/lib/switchman/active_record/migration.rb +7 -4
- data/lib/switchman/active_record/persistence.rb +7 -0
- data/lib/switchman/active_record/postgresql_adapter.rb +1 -1
- data/lib/switchman/active_record/predicate_builder.rb +1 -1
- data/lib/switchman/active_record/relation.rb +1 -1
- data/lib/switchman/active_record/test_fixtures.rb +43 -0
- data/lib/switchman/database_server.rb +45 -47
- data/lib/switchman/engine.rb +5 -1
- data/lib/switchman/r_spec_helper.rb +2 -17
- data/lib/switchman/standard_error.rb +4 -4
- data/lib/switchman/version.rb +1 -1
- data/lib/tasks/switchman.rake +9 -5
- metadata +10 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 326fba116e3a3801cdd40b47ede332a372dab86522cbeaab1a3b6b361eccce1f
|
4
|
+
data.tar.gz: 190457646d607aa444dbff867c67c5cd854817e8105efab44dd00fc782d1f21e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3843b4407a36b8f7f3aff2381dc6ee2e5f886169349794e679759a6d13d96b52e96e832caf78728f20df7d1d017701d7e7d8847c8a4620cd6b669ba8f25e9cc6
|
7
|
+
data.tar.gz: 8e667cd250473f5b885fea29888454a186bafa9f694138bf0cf9f6761962ef4bf2a85b595a9cd7d26fb3f6532b4b8b8be9d6362c01329a63f94272f63ee55b4b
|
@@ -39,12 +39,17 @@ module Switchman
|
|
39
39
|
# the first time we need a dummy dummy for re-entrancy to avoid looping on ourselves
|
40
40
|
@default ||= default
|
41
41
|
|
42
|
-
# Now find the actual record, if it exists
|
42
|
+
# Now find the actual record, if it exists
|
43
43
|
@default = begin
|
44
44
|
find_cached('default_shard') { Shard.where(default: true).take } || default
|
45
|
+
# If we are *super* early in boot, the connection pool won't exist; we don't want to fill in the default shard yet
|
46
|
+
rescue ::ActiveRecord::ConnectionNotEstablished
|
47
|
+
nil
|
48
|
+
# rescue the fake default if the table doesn't exist
|
45
49
|
rescue
|
46
50
|
default
|
47
51
|
end
|
52
|
+
return default unless @default
|
48
53
|
|
49
54
|
# make sure this is not erroneously cached
|
50
55
|
@default.database_server.remove_instance_variable(:@primary_shard) if @default.database_server.instance_variable_defined?(:@primary_shard)
|
@@ -59,34 +64,38 @@ module Switchman
|
|
59
64
|
|
60
65
|
def current(klass = ::ActiveRecord::Base)
|
61
66
|
klass ||= ::ActiveRecord::Base
|
62
|
-
klass.
|
67
|
+
klass.current_switchman_shard
|
63
68
|
end
|
64
69
|
|
65
70
|
def activate(shards)
|
66
71
|
activated_classes = activate!(shards)
|
67
72
|
yield
|
68
73
|
ensure
|
69
|
-
activated_classes
|
70
|
-
klass.connection_pool.shard_stack.pop
|
74
|
+
activated_classes&.each do |klass|
|
71
75
|
klass.connected_to_stack.pop
|
72
76
|
end
|
73
77
|
end
|
74
78
|
|
75
79
|
def activate!(shards)
|
76
|
-
activated_classes =
|
80
|
+
activated_classes = nil
|
77
81
|
shards.each do |klass, shard|
|
78
82
|
next if klass == UnshardedRecord
|
79
83
|
|
80
84
|
next unless klass.current_shard != shard.database_server.id.to_sym ||
|
81
|
-
klass.
|
85
|
+
klass.current_switchman_shard != shard
|
82
86
|
|
83
|
-
activated_classes << klass
|
84
|
-
klass.connected_to_stack << { shard: shard.database_server.id.to_sym, klasses: [klass] }
|
85
|
-
klass.connection_pool.shard_stack << shard
|
87
|
+
(activated_classes ||= []) << klass
|
88
|
+
klass.connected_to_stack << { shard: shard.database_server.id.to_sym, klasses: [klass], switchman_shard: shard }
|
86
89
|
end
|
87
90
|
activated_classes
|
88
91
|
end
|
89
92
|
|
93
|
+
def active_shards
|
94
|
+
sharded_models.map do |klass|
|
95
|
+
[klass, current(klass)]
|
96
|
+
end.compact.to_h
|
97
|
+
end
|
98
|
+
|
90
99
|
def lookup(id)
|
91
100
|
id_i = id.to_i
|
92
101
|
return current if id_i == current.id || id == 'self'
|
@@ -103,6 +112,11 @@ module Switchman
|
|
103
112
|
cached_shards[id]
|
104
113
|
end
|
105
114
|
|
115
|
+
def preload_cache
|
116
|
+
cached_shards.reverse_merge!(active_shards.values.index_by(&:id))
|
117
|
+
cached_shards.reverse_merge!(all.index_by(&:id))
|
118
|
+
end
|
119
|
+
|
106
120
|
def clear_cache
|
107
121
|
cached_shards.clear
|
108
122
|
end
|
@@ -111,14 +125,13 @@ module Switchman
|
|
111
125
|
#
|
112
126
|
# * +shards+ - an array or relation of Shards to iterate over
|
113
127
|
# * +classes+ - an array of classes to activate
|
114
|
-
# parallel: - true/false to execute in parallel, or
|
115
|
-
# sub-processes
|
116
|
-
#
|
117
|
-
#
|
118
|
-
# max_procs: - only run this many parallel processes at a time
|
128
|
+
# parallel: - true/false to execute in parallel, or an integer of how many
|
129
|
+
# sub-processes. Note that parallel invocation currently uses
|
130
|
+
# forking, so should be used sparingly because you cannot get
|
131
|
+
# results back
|
119
132
|
# exception: - :ignore, :raise, :defer (wait until the end and raise the first
|
120
133
|
# error), or a proc
|
121
|
-
def with_each_shard(*args, parallel: false,
|
134
|
+
def with_each_shard(*args, parallel: false, exception: :raise, &block)
|
122
135
|
raise ArgumentError, "wrong number of arguments (#{args.length} for 0...2)" if args.length > 2
|
123
136
|
|
124
137
|
return Array.wrap(yield) unless default.is_a?(Shard)
|
@@ -133,14 +146,13 @@ module Switchman
|
|
133
146
|
scope, classes = args
|
134
147
|
end
|
135
148
|
|
136
|
-
parallel =
|
149
|
+
parallel = [Environment.cpu_count || 2, 2].min if parallel == true
|
137
150
|
parallel = 0 if parallel == false || parallel.nil?
|
138
151
|
|
139
152
|
scope ||= Shard.all
|
140
153
|
scope = scope.order(::Arel.sql('database_server_id IS NOT NULL, database_server_id, id')) if ::ActiveRecord::Relation === scope && scope.order_values.empty?
|
141
154
|
|
142
|
-
if parallel
|
143
|
-
max_procs = determine_max_procs(max_procs, parallel)
|
155
|
+
if parallel > 1
|
144
156
|
if ::ActiveRecord::Relation === scope
|
145
157
|
# still need a post-uniq, cause the default database server could be NULL or Rails.env in the db
|
146
158
|
database_servers = scope.reorder('database_server_id').select(:database_server_id).distinct.
|
@@ -148,39 +160,11 @@ module Switchman
|
|
148
160
|
# nothing to do
|
149
161
|
return if database_servers.count.zero?
|
150
162
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
server_scope = server.shards.merge(scope)
|
155
|
-
if parallel == 1
|
156
|
-
subscopes = [server_scope]
|
157
|
-
else
|
158
|
-
subscopes = []
|
159
|
-
total = server_scope.count
|
160
|
-
ranges = []
|
161
|
-
server_scope.find_ids_in_ranges(batch_size: (total.to_f / parallel).ceil) do |min, max|
|
162
|
-
ranges << [min, max]
|
163
|
-
end
|
164
|
-
# create a half-open range on the last one
|
165
|
-
ranges.last[1] = nil
|
166
|
-
ranges.each do |min, max|
|
167
|
-
subscope = server_scope.where('id>=?', min)
|
168
|
-
subscope = subscope.where('id<=?', max) if max
|
169
|
-
subscopes << subscope
|
170
|
-
end
|
171
|
-
end
|
172
|
-
[server, subscopes]
|
173
|
-
end.to_h
|
163
|
+
scopes = database_servers.to_h do |server|
|
164
|
+
[server, server.shards.merge(scope)]
|
165
|
+
end
|
174
166
|
else
|
175
167
|
scopes = scope.group_by(&:database_server)
|
176
|
-
if parallel > 1
|
177
|
-
parallel = [(max_procs.to_f / scopes.count).ceil, parallel].min if max_procs
|
178
|
-
scopes = scopes.map do |(server, shards)|
|
179
|
-
[server, shards.in_groups(parallel, false).compact]
|
180
|
-
end.to_h
|
181
|
-
else
|
182
|
-
scopes = scopes.map { |(server, shards)| [server, [shards]] }.to_h
|
183
|
-
end
|
184
168
|
end
|
185
169
|
|
186
170
|
exception_pipes = []
|
@@ -206,80 +190,83 @@ module Switchman
|
|
206
190
|
end
|
207
191
|
|
208
192
|
# only one process; don't bother forking
|
209
|
-
|
210
|
-
return with_each_shard(scopes.first.last.first, classes, exception: exception,
|
211
|
-
&block)
|
212
|
-
end
|
193
|
+
return with_each_shard(scopes.first.last, classes, exception: exception, &block) if scopes.length == 1
|
213
194
|
|
214
195
|
# clear connections prior to forking (no more queries will be executed in the parent,
|
215
196
|
# and we want them gone so that we don't accidentally use them post-fork doing something
|
216
197
|
# silly like dealloc'ing prepared statements)
|
217
198
|
::ActiveRecord::Base.clear_all_connections!
|
218
199
|
|
219
|
-
scopes.each do |server,
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
args = args[0..max_length] if max_length >= 0
|
241
|
-
new_title = [bin, args, name].join(' ')
|
242
|
-
Process.setproctitle(new_title)
|
243
|
-
|
244
|
-
with_each_shard(subscope, classes, exception: exception, &block)
|
245
|
-
exception_pipe.last.close
|
246
|
-
rescue => e
|
247
|
-
begin
|
248
|
-
dumped = Marshal.dump(e)
|
249
|
-
rescue
|
250
|
-
# couldn't dump the exception; create a copy with just
|
251
|
-
# the message and the backtrace
|
252
|
-
e2 = e.class.new(e.message)
|
253
|
-
e2.set_backtrace(e.backtrace)
|
254
|
-
e2.instance_variable_set(:@active_shards, e.instance_variable_get(:@active_shards))
|
255
|
-
dumped = Marshal.dump(e2)
|
256
|
-
end
|
257
|
-
exception_pipe.last.set_encoding(dumped.encoding)
|
258
|
-
exception_pipe.last.write(dumped)
|
259
|
-
exception_pipe.last.flush
|
260
|
-
exception_pipe.last.close
|
261
|
-
exit! 1
|
262
|
-
end)
|
200
|
+
scopes.each do |server, subscope|
|
201
|
+
name = server.id
|
202
|
+
|
203
|
+
exception_pipe = IO.pipe
|
204
|
+
exception_pipes << exception_pipe
|
205
|
+
pid, io_in, io_out, io_err = Open4.pfork4(lambda do
|
206
|
+
Switchman.config[:on_fork_proc]&.call
|
207
|
+
|
208
|
+
# set a pretty name for the process title, up to 128 characters
|
209
|
+
# (we don't actually know the limit, depending on how the process
|
210
|
+
# was started)
|
211
|
+
# first, simplify the binary name by stripping directories,
|
212
|
+
# then truncate arguments as necessary
|
213
|
+
bin = File.basename($0) # Process.argv0 doesn't work on Ruby 2.5 (https://bugs.ruby-lang.org/issues/15887)
|
214
|
+
max_length = 128 - bin.length - name.length - 3
|
215
|
+
args = ARGV.join(' ')
|
216
|
+
args = args[0..max_length] if max_length >= 0
|
217
|
+
new_title = [bin, args, name].join(' ')
|
218
|
+
Process.setproctitle(new_title)
|
219
|
+
|
220
|
+
with_each_shard(subscope, classes, exception: exception, &block)
|
263
221
|
exception_pipe.last.close
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
#
|
278
|
-
|
279
|
-
|
280
|
-
|
222
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
223
|
+
begin
|
224
|
+
dumped = Marshal.dump(e)
|
225
|
+
dumped = nil if dumped.length > 64 * 1024
|
226
|
+
rescue
|
227
|
+
dumped = nil
|
228
|
+
end
|
229
|
+
|
230
|
+
if dumped.nil?
|
231
|
+
# couldn't dump the exception; create a copy with just
|
232
|
+
# the message and the backtrace
|
233
|
+
e2 = e.class.new(e.message)
|
234
|
+
backtrace = e.backtrace
|
235
|
+
# truncate excessively long backtraces
|
236
|
+
backtrace = backtrace[0...25] + ['...'] + backtrace[-25..] if backtrace.length > 50
|
237
|
+
e2.set_backtrace(backtrace)
|
238
|
+
e2.instance_variable_set(:@active_shards, e.instance_variable_get(:@active_shards))
|
239
|
+
dumped = Marshal.dump(e2)
|
281
240
|
end
|
241
|
+
exception_pipe.last.set_encoding(dumped.encoding)
|
242
|
+
exception_pipe.last.write(dumped)
|
243
|
+
exception_pipe.last.flush
|
244
|
+
exception_pipe.last.close
|
245
|
+
exit! 1
|
246
|
+
end)
|
247
|
+
exception_pipe.last.close
|
248
|
+
pids << pid
|
249
|
+
io_in.close # don't care about writing to stdin
|
250
|
+
out_fds << io_out
|
251
|
+
err_fds << io_err
|
252
|
+
pid_to_name_map[pid] = name
|
253
|
+
fd_to_name_map[io_out] = name
|
254
|
+
fd_to_name_map[io_err] = name
|
255
|
+
|
256
|
+
while pids.count >= parallel
|
257
|
+
while out_fds.count >= parallel
|
258
|
+
# wait for output if we've hit the parallel limit
|
259
|
+
wait_for_output.call
|
260
|
+
end
|
261
|
+
# we've gotten all the output from one fd so wait for its child process to exit
|
262
|
+
found_pid, status = Process.wait2
|
263
|
+
pids.delete(found_pid)
|
264
|
+
errors << pid_to_name_map[found_pid] if status.exitstatus != 0
|
282
265
|
end
|
266
|
+
# we've gotten all the output from one fd so wait for its child process to exit
|
267
|
+
found_pid, status = Process.wait2
|
268
|
+
pids.delete(found_pid)
|
269
|
+
errors << pid_to_name_map[found_pid] if status.exitstatus != 0
|
283
270
|
end
|
284
271
|
|
285
272
|
wait_for_output.call while out_fds.any? || err_fds.any?
|
@@ -402,7 +389,7 @@ module Switchman
|
|
402
389
|
signed_id_operation(local_id) do |id|
|
403
390
|
return nil if id > IDS_PER_SHARD
|
404
391
|
|
405
|
-
$1.to_i * IDS_PER_SHARD + id
|
392
|
+
($1.to_i * IDS_PER_SHARD) + id
|
406
393
|
end
|
407
394
|
when Integer, /^-?\d+$/
|
408
395
|
any_id.to_i
|
@@ -480,23 +467,6 @@ module Switchman
|
|
480
467
|
shard || source_shard || Shard.current
|
481
468
|
end
|
482
469
|
|
483
|
-
# given the provided option, determines whether we need to (and whether
|
484
|
-
# it's possible) to determine a reasonable default.
|
485
|
-
def determine_max_procs(max_procs_input, parallel_input = 2)
|
486
|
-
max_procs = nil
|
487
|
-
if max_procs_input
|
488
|
-
max_procs = max_procs_input.to_i
|
489
|
-
max_procs = nil if max_procs.zero?
|
490
|
-
else
|
491
|
-
return 1 if parallel_input.nil? || parallel_input < 1
|
492
|
-
|
493
|
-
cpus = Environment.cpu_count
|
494
|
-
max_procs = cpus * parallel_input if cpus&.positive?
|
495
|
-
end
|
496
|
-
|
497
|
-
max_procs
|
498
|
-
end
|
499
|
-
|
500
470
|
private
|
501
471
|
|
502
472
|
def add_sharded_model(klass)
|
@@ -505,7 +475,7 @@ module Switchman
|
|
505
475
|
end
|
506
476
|
|
507
477
|
def initialize_sharding
|
508
|
-
full_connects_to_hash = DatabaseServer.all.
|
478
|
+
full_connects_to_hash = DatabaseServer.all.to_h { |db| [db.id.to_sym, db.connects_to_hash] }
|
509
479
|
sharded_models.each do |klass|
|
510
480
|
connects_to_hash = full_connects_to_hash.deep_dup
|
511
481
|
if klass == UnshardedRecord
|
@@ -596,6 +566,10 @@ module Switchman
|
|
596
566
|
Shard.default
|
597
567
|
end
|
598
568
|
|
569
|
+
def original_id
|
570
|
+
id
|
571
|
+
end
|
572
|
+
|
599
573
|
def activate(*classes, &block)
|
600
574
|
shards = hashify_classes(classes)
|
601
575
|
Shard.activate(shards, &block)
|
@@ -668,7 +642,7 @@ module Switchman
|
|
668
642
|
return nil unless local_id
|
669
643
|
|
670
644
|
self.class.signed_id_operation(local_id) do |abs_id|
|
671
|
-
abs_id + id * IDS_PER_SHARD
|
645
|
+
abs_id + (id * IDS_PER_SHARD)
|
672
646
|
end
|
673
647
|
end
|
674
648
|
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
class AddDefaultShardIndex < ActiveRecord::Migration[4.2]
|
4
4
|
def change
|
5
|
-
Switchman::Shard.where(default: nil).update_all(default: false)
|
5
|
+
Switchman::Shard.where(default: nil).update_all(default: false) if Switchman::Shard.current.default?
|
6
6
|
change_column_default :switchman_shards, :default, false
|
7
7
|
change_column_null :switchman_shards, :default, false
|
8
8
|
options = if connection.adapter_name == 'PostgreSQL'
|
@@ -4,7 +4,7 @@ class AddTimestampsToShards < ActiveRecord::Migration[4.2]
|
|
4
4
|
disable_ddl_transaction!
|
5
5
|
|
6
6
|
def change
|
7
|
-
add_timestamps :switchman_shards, null: true
|
7
|
+
add_timestamps :switchman_shards, null: true, if_not_exists: true
|
8
8
|
now = Time.now.utc
|
9
9
|
Switchman::Shard.update_all(updated_at: now, created_at: now) if Switchman::Shard.current.default?
|
10
10
|
change_column_null :switchman_shards, :updated_at, false
|
@@ -40,15 +40,6 @@ module Switchman
|
|
40
40
|
ensure
|
41
41
|
@last_query_at = Time.now
|
42
42
|
end
|
43
|
-
|
44
|
-
private
|
45
|
-
|
46
|
-
def id_value_for_database(value)
|
47
|
-
return super unless value.class.sharded_primary_key?
|
48
|
-
|
49
|
-
# do this the Rails 4.2 way, so that if Shard.current != self.shard, the id gets transposed
|
50
|
-
quote(value.id)
|
51
|
-
end
|
52
43
|
end
|
53
44
|
end
|
54
45
|
end
|
@@ -40,7 +40,11 @@ module Switchman
|
|
40
40
|
if sharded_column?(attr_name)
|
41
41
|
owner << <<-RUBY
|
42
42
|
def global_#{attr_name}
|
43
|
-
|
43
|
+
raw_value = original_#{attr_name}
|
44
|
+
return nil if raw_value.nil?
|
45
|
+
return raw_value if raw_value > ::Switchman::Shard::IDS_PER_SHARD
|
46
|
+
|
47
|
+
::Switchman::Shard.global_id_for(raw_value, shard)
|
44
48
|
end
|
45
49
|
RUBY
|
46
50
|
else
|
@@ -52,7 +56,9 @@ module Switchman
|
|
52
56
|
if sharded_column?(attr_name)
|
53
57
|
owner << <<-RUBY
|
54
58
|
def local_#{attr_name}
|
55
|
-
|
59
|
+
raw_value = original_#{attr_name}
|
60
|
+
return nil if raw_value.nil?
|
61
|
+
return raw_value % ::Switchman::Shard::IDS_PER_SHARD
|
56
62
|
end
|
57
63
|
RUBY
|
58
64
|
else
|
@@ -104,7 +110,22 @@ module Switchman
|
|
104
110
|
alias_method 'original_#{attr_name}', '#{attr_name}'
|
105
111
|
# and replace with one that transposes the id
|
106
112
|
def #{attr_name}
|
107
|
-
|
113
|
+
raw_value = original_#{attr_name}
|
114
|
+
return nil if raw_value.nil?
|
115
|
+
|
116
|
+
abs_raw_value = raw_value.abs
|
117
|
+
current_shard = ::Switchman::Shard.current(#{connection_classes_code_for_reflection(reflection)})
|
118
|
+
same_shard = shard == current_shard
|
119
|
+
return raw_value if same_shard && abs_raw_value < ::Switchman::Shard::IDS_PER_SHARD
|
120
|
+
|
121
|
+
value_shard_id = abs_raw_value / ::Switchman::Shard::IDS_PER_SHARD
|
122
|
+
# this is a stupid case when someone stuffed a global id for the current shard in instead
|
123
|
+
# of a local id
|
124
|
+
return raw_value % ::Switchman::Shard::IDS_PER_SHARD if value_shard_id == current_shard.id
|
125
|
+
return raw_value if !same_shard && abs_raw_value > ::Switchman::Shard::IDS_PER_SHARD
|
126
|
+
return shard.global_id_for(raw_value) if !same_shard && abs_raw_value < ::Switchman::Shard::IDS_PER_SHARD
|
127
|
+
|
128
|
+
::Switchman::Shard.relative_id_for(raw_value, shard, current_shard)
|
108
129
|
end
|
109
130
|
|
110
131
|
alias_method 'original_#{attr_name}=', '#{attr_name}='
|
@@ -77,17 +77,27 @@ module Switchman
|
|
77
77
|
|
78
78
|
default_shard
|
79
79
|
end
|
80
|
+
|
81
|
+
def current_switchman_shard
|
82
|
+
connected_to_stack.reverse_each do |hash|
|
83
|
+
return hash[:switchman_shard] if hash[:switchman_shard] && hash[:klasses].include?(connection_classes)
|
84
|
+
end
|
85
|
+
|
86
|
+
Shard.default
|
87
|
+
end
|
80
88
|
end
|
81
89
|
|
82
|
-
def self.
|
90
|
+
def self.prepended(klass)
|
83
91
|
klass.singleton_class.prepend(ClassMethods)
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
92
|
+
end
|
93
|
+
|
94
|
+
def _run_initialize_callbacks
|
95
|
+
@shard ||= if self.class.sharded_primary_key?
|
96
|
+
Shard.shard_for(self[self.class.primary_key], Shard.current(self.class.connection_classes))
|
97
|
+
else
|
98
|
+
Shard.current(self.class.connection_classes)
|
99
|
+
end
|
100
|
+
super
|
91
101
|
end
|
92
102
|
|
93
103
|
def shard
|
@@ -107,12 +117,12 @@ module Switchman
|
|
107
117
|
|
108
118
|
def save(*, **)
|
109
119
|
@shard_set_in_stone = true
|
110
|
-
|
120
|
+
super
|
111
121
|
end
|
112
122
|
|
113
123
|
def save!(*, **)
|
114
124
|
@shard_set_in_stone = true
|
115
|
-
|
125
|
+
super
|
116
126
|
end
|
117
127
|
|
118
128
|
def destroy
|
@@ -134,6 +144,12 @@ module Switchman
|
|
134
144
|
end
|
135
145
|
end
|
136
146
|
|
147
|
+
def with_transaction_returning_status
|
148
|
+
shard.activate(self.class.connection_classes) do
|
149
|
+
super
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
137
153
|
def hash
|
138
154
|
self.class.sharded_primary_key? ? self.class.hash ^ global_id.hash : super
|
139
155
|
end
|
@@ -149,19 +165,20 @@ module Switchman
|
|
149
165
|
copy
|
150
166
|
end
|
151
167
|
|
152
|
-
def
|
153
|
-
|
168
|
+
def update_columns(*)
|
169
|
+
db = shard.database_server
|
170
|
+
return db.unguard { super } if ::GuardRail.environment != db.guard_rail_environment
|
154
171
|
|
155
|
-
|
156
|
-
self.class.connection.quote(id)
|
172
|
+
super
|
157
173
|
end
|
158
174
|
|
159
|
-
def
|
160
|
-
|
161
|
-
|
162
|
-
|
175
|
+
def id_for_database
|
176
|
+
if self.class.sharded_primary_key?
|
177
|
+
# It's an int, so so it's safe to just return it without passing it through anything else
|
178
|
+
# In theory we should do `@attributes[@primary_key].type.serialize(id)`, but that seems to have surprising side-effects
|
179
|
+
id
|
163
180
|
else
|
164
|
-
|
181
|
+
super
|
165
182
|
end
|
166
183
|
end
|
167
184
|
|
@@ -172,7 +189,7 @@ module Switchman
|
|
172
189
|
if reflection
|
173
190
|
if reflection.options[:polymorphic]
|
174
191
|
begin
|
175
|
-
read_attribute(reflection.foreign_type)&.constantize&.connection_classes
|
192
|
+
read_attribute(reflection.foreign_type)&.constantize&.connection_classes || ::ActiveRecord::Base
|
176
193
|
rescue NameError
|
177
194
|
# in case someone is abusing foreign_type to not point to an actual class
|
178
195
|
::ActiveRecord::Base
|
@@ -182,7 +199,7 @@ module Switchman
|
|
182
199
|
reflection.klass.connection_classes
|
183
200
|
end
|
184
201
|
else
|
185
|
-
connection_classes
|
202
|
+
self.class.connection_classes
|
186
203
|
end
|
187
204
|
end
|
188
205
|
end
|
@@ -83,7 +83,7 @@ module Switchman
|
|
83
83
|
if opts[:association]
|
84
84
|
key_ids = calculated_data.collect { |row| row[opts[:group_aliases].first] }
|
85
85
|
key_records = opts[:association].klass.base_class.where(id: key_ids)
|
86
|
-
key_records = key_records.
|
86
|
+
key_records = key_records.to_h { |r| [Shard.relative_id_for(r, shard, target_shard), r] }
|
87
87
|
end
|
88
88
|
|
89
89
|
calculated_data.map do |row|
|
@@ -5,18 +5,6 @@ require 'switchman/errors'
|
|
5
5
|
module Switchman
|
6
6
|
module ActiveRecord
|
7
7
|
module ConnectionPool
|
8
|
-
def shard
|
9
|
-
shard_stack.last || Shard.default
|
10
|
-
end
|
11
|
-
|
12
|
-
def shard_stack
|
13
|
-
unless (shard_stack = Thread.current.thread_variable_get(tls_key))
|
14
|
-
shard_stack = Concurrent::Array.new
|
15
|
-
Thread.current.thread_variable_set(tls_key, shard_stack)
|
16
|
-
end
|
17
|
-
shard_stack
|
18
|
-
end
|
19
|
-
|
20
8
|
def default_schema
|
21
9
|
connection unless @schemas
|
22
10
|
# default shard will not switch databases immediately, so it won't be set yet
|
@@ -26,15 +14,15 @@ module Switchman
|
|
26
14
|
|
27
15
|
def checkout_new_connection
|
28
16
|
conn = super
|
29
|
-
conn.shard =
|
17
|
+
conn.shard = current_shard
|
30
18
|
conn
|
31
19
|
end
|
32
20
|
|
33
21
|
def connection(switch_shard: true)
|
34
22
|
conn = super()
|
35
|
-
raise NonExistentShardError if
|
23
|
+
raise NonExistentShardError if current_shard.new_record?
|
36
24
|
|
37
|
-
switch_database(conn) if conn.shard !=
|
25
|
+
switch_database(conn) if conn.shard != current_shard && switch_shard
|
38
26
|
conn
|
39
27
|
end
|
40
28
|
|
@@ -44,26 +32,18 @@ module Switchman
|
|
44
32
|
flush
|
45
33
|
end
|
46
34
|
|
47
|
-
def remove_shard!(shard)
|
48
|
-
synchronize do
|
49
|
-
# The shard might be currently active, so we need to update our own shard
|
50
|
-
self.shard = Shard.default if self.shard == shard
|
51
|
-
# Update out any connections that may be using this shard
|
52
|
-
@connections.each do |conn|
|
53
|
-
# This will also update the connection's shard to the default shard
|
54
|
-
switch_database(conn) if conn.shard == shard
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
35
|
def switch_database(conn)
|
60
|
-
@schemas = conn.current_schemas if !@schemas && conn.adapter_name == 'PostgreSQL' && !
|
36
|
+
@schemas = conn.current_schemas if !@schemas && conn.adapter_name == 'PostgreSQL' && !current_shard.database_server.config[:shard_name]
|
61
37
|
|
62
|
-
conn.shard =
|
38
|
+
conn.shard = current_shard
|
63
39
|
end
|
64
40
|
|
65
41
|
private
|
66
42
|
|
43
|
+
def current_shard
|
44
|
+
connection_klass.current_switchman_shard
|
45
|
+
end
|
46
|
+
|
67
47
|
def tls_key
|
68
48
|
"#{object_id}_shard".to_sym
|
69
49
|
end
|
@@ -14,16 +14,19 @@ module Switchman
|
|
14
14
|
|
15
15
|
def connection
|
16
16
|
conn = super
|
17
|
-
::ActiveRecord::Base.connection_pool.switch_database(conn) if conn.shard != ::ActiveRecord::Base.
|
17
|
+
::ActiveRecord::Base.connection_pool.switch_database(conn) if conn.shard != ::ActiveRecord::Base.current_switchman_shard
|
18
18
|
conn
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
22
|
module Migrator
|
23
|
-
# significant change:
|
23
|
+
# significant change: just return MIGRATOR_SALT directly
|
24
|
+
# especially if you're going through pgbouncer, the database
|
25
|
+
# name you're accessing may not be consistent. it is NOT allowed
|
26
|
+
# to run migrations against multiple shards in the same database
|
27
|
+
# concurrently
|
24
28
|
def generate_migrator_advisory_lock_id
|
25
|
-
|
26
|
-
::ActiveRecord::Migrator::MIGRATOR_SALT * shard_name_hash
|
29
|
+
::ActiveRecord::Migrator::MIGRATOR_SALT
|
27
30
|
end
|
28
31
|
|
29
32
|
# significant change: strip out prefer_secondary from config
|
@@ -11,6 +11,13 @@ module Switchman
|
|
11
11
|
def update_columns(*)
|
12
12
|
shard.activate(self.class.connection_classes) { super }
|
13
13
|
end
|
14
|
+
|
15
|
+
def delete
|
16
|
+
db = shard.database_server
|
17
|
+
return db.unguard { super } unless ::GuardRail.environment == db.guard_rail_environment
|
18
|
+
|
19
|
+
super
|
20
|
+
end
|
14
21
|
end
|
15
22
|
end
|
16
23
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Switchman
|
4
|
+
module ActiveRecord
|
5
|
+
module TestFixtures
|
6
|
+
FORBIDDEN_DB_ENVS = %i[development production].freeze
|
7
|
+
def setup_fixtures(config = ::ActiveRecord::Base)
|
8
|
+
super
|
9
|
+
|
10
|
+
return unless run_in_transaction?
|
11
|
+
|
12
|
+
# Replace the one that activerecord natively uses with a switchman-optimized one
|
13
|
+
::ActiveSupport::Notifications.unsubscribe(@connection_subscriber)
|
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
|
19
|
+
|
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
|
26
|
+
|
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
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def enlist_fixture_connections
|
37
|
+
setup_shared_connection_pool
|
38
|
+
|
39
|
+
::ActiveRecord::Base.connection_handler.connection_pool_list.reject { |cp| FORBIDDEN_DB_ENVS.include?(cp.db_config.env_name.to_sym) }.map(&:connection)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -59,7 +59,7 @@ module Switchman
|
|
59
59
|
end
|
60
60
|
|
61
61
|
def database_servers
|
62
|
-
|
62
|
+
if !@database_servers || @database_servers.empty?
|
63
63
|
@database_servers = {}.with_indifferent_access
|
64
64
|
::ActiveRecord::Base.configurations.configurations.each do |config|
|
65
65
|
if config.name.include?('/')
|
@@ -89,13 +89,13 @@ module Switchman
|
|
89
89
|
end
|
90
90
|
|
91
91
|
def connects_to_hash
|
92
|
-
self.class.all_roles.
|
92
|
+
self.class.all_roles.to_h do |role|
|
93
93
|
config_role = role
|
94
94
|
config_role = :primary unless roles.include?(role)
|
95
95
|
config_name = :"#{id}/#{config_role}"
|
96
96
|
config_name = :primary if id == ::Rails.env && config_role == :primary
|
97
97
|
[role.to_sym, config_name]
|
98
|
-
end
|
98
|
+
end
|
99
99
|
end
|
100
100
|
|
101
101
|
def destroy
|
@@ -186,58 +186,56 @@ module Switchman
|
|
186
186
|
|
187
187
|
name ||= "#{config[:database]}_shard_#{id}"
|
188
188
|
|
189
|
+
schema_already_existed = false
|
190
|
+
shard = nil
|
189
191
|
Shard.connection.transaction do
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
if ::ActiveRecord::Base.connection.select_value("SELECT 1 FROM pg_namespace WHERE nspname=#{::ActiveRecord::Base.connection.quote(name)}")
|
201
|
-
schema_already_existed = true
|
202
|
-
raise 'This schema already exists; cannot overwrite'
|
203
|
-
end
|
204
|
-
Array(create_statement.call).each do |stmt|
|
205
|
-
::ActiveRecord::Base.connection.execute(stmt)
|
206
|
-
end
|
192
|
+
self.class.creating_new_shard = true
|
193
|
+
DatabaseServer.send(:reference_role, :deploy)
|
194
|
+
::ActiveRecord::Base.connected_to(shard: self.id.to_sym, role: :deploy) do
|
195
|
+
shard = Shard.create!(id: id,
|
196
|
+
name: name,
|
197
|
+
database_server_id: self.id)
|
198
|
+
if create_statement
|
199
|
+
if ::ActiveRecord::Base.connection.select_value("SELECT 1 FROM pg_namespace WHERE nspname=#{::ActiveRecord::Base.connection.quote(name)}")
|
200
|
+
schema_already_existed = true
|
201
|
+
raise 'This schema already exists; cannot overwrite'
|
207
202
|
end
|
208
|
-
|
209
|
-
|
210
|
-
end
|
203
|
+
Array(create_statement.call).each do |stmt|
|
204
|
+
::ActiveRecord::Base.connection.execute(stmt)
|
211
205
|
end
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
206
|
+
end
|
207
|
+
if config[:adapter] == 'postgresql'
|
208
|
+
old_proc = ::ActiveRecord::Base.connection.raw_connection.set_notice_processor do
|
209
|
+
end
|
210
|
+
end
|
211
|
+
old_verbose = ::ActiveRecord::Migration.verbose
|
212
|
+
::ActiveRecord::Migration.verbose = false
|
213
|
+
|
214
|
+
unless schema == false
|
215
|
+
shard.activate do
|
216
|
+
reset_column_information
|
217
|
+
|
218
|
+
::ActiveRecord::Base.connection.transaction(requires_new: true) do
|
219
|
+
::ActiveRecord::Base.connection.migration_context.migrate
|
226
220
|
end
|
221
|
+
reset_column_information
|
222
|
+
::ActiveRecord::Base.descendants.reject do |m|
|
223
|
+
m <= UnshardedRecord || !m.table_exists?
|
224
|
+
end.each(&:define_attribute_methods)
|
227
225
|
end
|
228
|
-
ensure
|
229
|
-
::ActiveRecord::Migration.verbose = old_verbose
|
230
|
-
::ActiveRecord::Base.connection.raw_connection.set_notice_processor(&old_proc) if old_proc
|
231
226
|
end
|
232
|
-
shard
|
233
|
-
rescue
|
234
|
-
shard.destroy
|
235
|
-
shard.drop_database rescue nil unless schema_already_existed
|
236
|
-
reset_column_information unless schema == false rescue nil
|
237
|
-
raise
|
238
227
|
ensure
|
239
|
-
|
228
|
+
::ActiveRecord::Migration.verbose = old_verbose
|
229
|
+
::ActiveRecord::Base.connection.raw_connection.set_notice_processor(&old_proc) if old_proc
|
240
230
|
end
|
231
|
+
shard
|
232
|
+
rescue
|
233
|
+
shard&.destroy
|
234
|
+
shard&.drop_database rescue nil unless schema_already_existed
|
235
|
+
reset_column_information unless schema == false rescue nil
|
236
|
+
raise
|
237
|
+
ensure
|
238
|
+
self.class.creating_new_shard = false
|
241
239
|
end
|
242
240
|
end
|
243
241
|
|
data/lib/switchman/engine.rb
CHANGED
@@ -90,6 +90,7 @@ module Switchman
|
|
90
90
|
require 'switchman/active_record/statement_cache'
|
91
91
|
require 'switchman/active_record/tasks/database_tasks'
|
92
92
|
require 'switchman/active_record/type_caster'
|
93
|
+
require 'switchman/active_record/test_fixtures'
|
93
94
|
require 'switchman/arel'
|
94
95
|
require 'switchman/call_super'
|
95
96
|
require 'switchman/rails'
|
@@ -101,7 +102,7 @@ module Switchman
|
|
101
102
|
self.default_shard = ::Rails.env.to_sym
|
102
103
|
self.default_role = :primary
|
103
104
|
|
104
|
-
|
105
|
+
prepend ActiveRecord::Base
|
105
106
|
include ActiveRecord::AttributeMethods
|
106
107
|
include ActiveRecord::Persistence
|
107
108
|
singleton_class.prepend ActiveRecord::ModelSchema::ClassMethods
|
@@ -150,9 +151,12 @@ module Switchman
|
|
150
151
|
::ActiveRecord::Relation.include(CallSuper)
|
151
152
|
|
152
153
|
::ActiveRecord::PredicateBuilder::AssociationQueryValue.prepend(ActiveRecord::PredicateBuilder::AssociationQueryValue)
|
154
|
+
::ActiveRecord::PredicateBuilder::PolymorphicArrayValue.prepend(ActiveRecord::PredicateBuilder::AssociationQueryValue)
|
153
155
|
|
154
156
|
::ActiveRecord::Tasks::DatabaseTasks.singleton_class.prepend(ActiveRecord::Tasks::DatabaseTasks)
|
155
157
|
|
158
|
+
::ActiveRecord::TestFixtures.prepend(ActiveRecord::TestFixtures)
|
159
|
+
|
156
160
|
::ActiveRecord::TypeCaster::Map.include(ActiveRecord::TypeCaster::Map)
|
157
161
|
::ActiveRecord::TypeCaster::Connection.include(ActiveRecord::TypeCaster::Connection)
|
158
162
|
|
@@ -111,28 +111,12 @@ module Switchman
|
|
111
111
|
Shard.default(reload: true)
|
112
112
|
@shard1 = Shard.find(@shard1.id)
|
113
113
|
@shard2 = Shard.find(@shard2.id)
|
114
|
-
shards = [@shard2]
|
115
|
-
shards << @shard1 unless @shard1.database_server == Shard.default.database_server
|
116
|
-
shards.each do |shard|
|
117
|
-
shard.activate do
|
118
|
-
::ActiveRecord::Base.connection.begin_transaction joinable: false
|
119
|
-
end
|
120
|
-
end
|
121
114
|
end
|
122
115
|
end
|
123
116
|
|
124
117
|
klass.after do
|
125
118
|
next if @@sharding_failed
|
126
119
|
|
127
|
-
if use_transactional_tests
|
128
|
-
shards = [@shard2]
|
129
|
-
shards << @shard1 unless @shard1.database_server == Shard.default.database_server
|
130
|
-
shards.each do |shard|
|
131
|
-
shard.activate do
|
132
|
-
::ActiveRecord::Base.connection.rollback_transaction if ::ActiveRecord::Base.connection.transaction_open?
|
133
|
-
end
|
134
|
-
end
|
135
|
-
end
|
136
120
|
# clean up after specs
|
137
121
|
DatabaseServer.all.each do |ds|
|
138
122
|
if ds.fake? && ds != @shard2.database_server
|
@@ -143,7 +127,8 @@ module Switchman
|
|
143
127
|
end
|
144
128
|
|
145
129
|
klass.after(:all) do
|
146
|
-
|
130
|
+
# Don't truncate because that can create some fun cross-connection lock contention
|
131
|
+
Shard.delete_all
|
147
132
|
Switchman.cache.delete('default_shard')
|
148
133
|
Shard.default(reload: true)
|
149
134
|
end
|
@@ -10,10 +10,10 @@ module Switchman
|
|
10
10
|
return
|
11
11
|
end
|
12
12
|
|
13
|
-
|
14
|
-
@active_shards = Shard.
|
15
|
-
|
16
|
-
|
13
|
+
begin
|
14
|
+
@active_shards = Shard.active_shards if defined?(Shard)
|
15
|
+
rescue ::ActiveRecord::ConnectionNotEstablished
|
16
|
+
# If we hit an error really early in boot, activerecord may not be initialized yet
|
17
17
|
end
|
18
18
|
|
19
19
|
super
|
data/lib/switchman/version.rb
CHANGED
data/lib/tasks/switchman.rake
CHANGED
@@ -46,7 +46,7 @@ module Switchman
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def self.options
|
49
|
-
{ parallel: ENV['PARALLEL'].to_i
|
49
|
+
{ parallel: ENV['PARALLEL'].to_i }
|
50
50
|
end
|
51
51
|
|
52
52
|
# classes - an array or proc, to activate as the current shard during the
|
@@ -80,14 +80,14 @@ module Switchman
|
|
80
80
|
puts "Exception from #{e.current_shard.id}: #{e.current_shard.description}" if options[:parallel] != 0
|
81
81
|
raise
|
82
82
|
|
83
|
-
|
83
|
+
# ::ActiveRecord::Base.configurations = old_configurations
|
84
84
|
end
|
85
85
|
end
|
86
86
|
end
|
87
87
|
end
|
88
88
|
|
89
89
|
%w[db:migrate db:migrate:up db:migrate:down db:rollback].each do |task_name|
|
90
|
-
shardify_task(task_name
|
90
|
+
shardify_task(task_name)
|
91
91
|
end
|
92
92
|
|
93
93
|
def self.shard_scope(scope, raw_shard_ids)
|
@@ -201,14 +201,18 @@ module Switchman
|
|
201
201
|
module PostgreSQLDatabaseTasks
|
202
202
|
def structure_dump(filename, extra_flags = nil)
|
203
203
|
set_psql_env
|
204
|
-
args = ['-
|
204
|
+
args = ['--schema-only', '--no-privileges', '--no-owner', '--file', filename]
|
205
205
|
args.concat(Array(extra_flags)) if extra_flags
|
206
206
|
shard = Shard.current.name
|
207
207
|
serialized_search_path = shard
|
208
208
|
args << "--schema=#{Shellwords.escape(shard)}"
|
209
209
|
|
210
|
-
|
210
|
+
ignore_tables = ::ActiveRecord::SchemaDumper.ignore_tables
|
211
|
+
args += ignore_tables.flat_map { |table| ['-T', table] } if ignore_tables.any?
|
212
|
+
|
213
|
+
args << db_config.database
|
211
214
|
run_cmd('pg_dump', args, 'dumping')
|
215
|
+
remove_sql_header_comments(filename)
|
212
216
|
File.open(filename, 'a') { |f| f << "SET search_path TO #{serialized_search_path};\n\n" }
|
213
217
|
end
|
214
218
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: switchman
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.0.
|
4
|
+
version: 3.0.7
|
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:
|
13
|
+
date: 2022-02-23 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activerecord
|
@@ -18,7 +18,7 @@ dependencies:
|
|
18
18
|
requirements:
|
19
19
|
- - ">="
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version:
|
21
|
+
version: 6.1.4
|
22
22
|
- - "<"
|
23
23
|
- !ruby/object:Gem::Version
|
24
24
|
version: '6.2'
|
@@ -28,7 +28,7 @@ dependencies:
|
|
28
28
|
requirements:
|
29
29
|
- - ">="
|
30
30
|
- !ruby/object:Gem::Version
|
31
|
-
version:
|
31
|
+
version: 6.1.4
|
32
32
|
- - "<"
|
33
33
|
- !ruby/object:Gem::Version
|
34
34
|
version: '6.2'
|
@@ -86,14 +86,14 @@ dependencies:
|
|
86
86
|
requirements:
|
87
87
|
- - "~>"
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version:
|
89
|
+
version: 2.3.0
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version:
|
96
|
+
version: 2.3.0
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
98
|
name: byebug
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -275,6 +275,7 @@ files:
|
|
275
275
|
- lib/switchman/active_record/statement_cache.rb
|
276
276
|
- lib/switchman/active_record/table_definition.rb
|
277
277
|
- lib/switchman/active_record/tasks/database_tasks.rb
|
278
|
+
- lib/switchman/active_record/test_fixtures.rb
|
278
279
|
- lib/switchman/active_record/type_caster.rb
|
279
280
|
- lib/switchman/active_support/cache.rb
|
280
281
|
- lib/switchman/arel.rb
|
@@ -297,7 +298,8 @@ files:
|
|
297
298
|
homepage: http://www.instructure.com/
|
298
299
|
licenses:
|
299
300
|
- MIT
|
300
|
-
metadata:
|
301
|
+
metadata:
|
302
|
+
rubygems_mfa_required: 'true'
|
301
303
|
post_install_message:
|
302
304
|
rdoc_options: []
|
303
305
|
require_paths:
|
@@ -313,7 +315,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
313
315
|
- !ruby/object:Gem::Version
|
314
316
|
version: '0'
|
315
317
|
requirements: []
|
316
|
-
rubygems_version: 3.
|
318
|
+
rubygems_version: 3.1.4
|
317
319
|
signing_key:
|
318
320
|
specification_version: 4
|
319
321
|
summary: Rails sharding magic
|