switchman 3.3.6 → 3.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/switchman/active_record/associations.rb +1 -1
- data/lib/switchman/active_record/attribute_methods.rb +5 -7
- data/lib/switchman/active_record/base.rb +30 -3
- data/lib/switchman/active_record/migration.rb +23 -0
- data/lib/switchman/active_record/persistence.rb +8 -0
- data/lib/switchman/active_record/relation.rb +1 -1
- data/lib/switchman/database_server.rb +2 -26
- data/lib/switchman/errors.rb +6 -0
- data/lib/switchman/parallel.rb +1 -1
- data/lib/switchman/version.rb +1 -1
- data/lib/tasks/switchman.rake +5 -5
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: be3660461c9c52c63b304de0bb3e5a18e3fe3cdfe7d5f61802b2db2b232f97be
|
4
|
+
data.tar.gz: 47248f2cb8c6c35396d537e7bd1f938399811d11eb1a07a96778654ccf5dc3f9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 60d5b3eb13458b61bbdc8d2a729e10f870284d35fa0694612d793ed8ff9fd15667777dda2e4d28e4fb435a707aff0ca483e51032b396d7496b5d3792e6d6be16
|
7
|
+
data.tar.gz: fa52c0f4ddf8fea8602917d64f165602ee14ffb9ea72f609a7f9d11e2ac511b5ba3cae9a8ae9915791c268adc0c9f9ef12f142817d0e472bf1c3a17b1f3e262c
|
@@ -50,7 +50,7 @@ module Switchman
|
|
50
50
|
def shard
|
51
51
|
if @owner.class.sharded_column?(@reflection.foreign_key) &&
|
52
52
|
(foreign_id = @owner[@reflection.foreign_key])
|
53
|
-
Shard.shard_for(foreign_id, @owner.
|
53
|
+
Shard.shard_for(foreign_id, @owner.loaded_from_shard)
|
54
54
|
else
|
55
55
|
super
|
56
56
|
end
|
@@ -46,7 +46,6 @@ module Switchman
|
|
46
46
|
raise if connection.open_transactions.positive?
|
47
47
|
end
|
48
48
|
|
49
|
-
# rubocop:disable Naming/MethodParameterName
|
50
49
|
def define_cached_method(owner, name, namespace:, as:, &block)
|
51
50
|
if ::Rails.version < '7.0'
|
52
51
|
yield owner
|
@@ -55,7 +54,6 @@ module Switchman
|
|
55
54
|
owner.define_cached_method(name, namespace: namespace, as: as, &block)
|
56
55
|
end
|
57
56
|
end
|
58
|
-
# rubocop:enable Naming/MethodParameterName
|
59
57
|
|
60
58
|
def define_method_global_attribute(attr_name, owner:)
|
61
59
|
if sharded_column?(attr_name)
|
@@ -136,7 +134,7 @@ module Switchman
|
|
136
134
|
|
137
135
|
abs_raw_value = raw_value.abs
|
138
136
|
current_shard = ::Switchman::Shard.current(#{attr_connection_class})
|
139
|
-
same_shard =
|
137
|
+
same_shard = loaded_from_shard == current_shard
|
140
138
|
return raw_value if same_shard && abs_raw_value < ::Switchman::Shard::IDS_PER_SHARD
|
141
139
|
|
142
140
|
value_shard_id = abs_raw_value / ::Switchman::Shard::IDS_PER_SHARD
|
@@ -144,9 +142,9 @@ module Switchman
|
|
144
142
|
# of a local id
|
145
143
|
return raw_value % ::Switchman::Shard::IDS_PER_SHARD if value_shard_id == current_shard.id
|
146
144
|
return raw_value if !same_shard && abs_raw_value > ::Switchman::Shard::IDS_PER_SHARD
|
147
|
-
return
|
145
|
+
return loaded_from_shard.global_id_for(raw_value) if !same_shard && abs_raw_value < ::Switchman::Shard::IDS_PER_SHARD
|
148
146
|
|
149
|
-
::Switchman::Shard.relative_id_for(raw_value,
|
147
|
+
::Switchman::Shard.relative_id_for(raw_value, loaded_from_shard, current_shard)
|
150
148
|
end
|
151
149
|
RUBY
|
152
150
|
end
|
@@ -173,7 +171,7 @@ module Switchman
|
|
173
171
|
def build_sharded_setter(attr_name, attr_field, attr_connection_class)
|
174
172
|
<<-RUBY
|
175
173
|
def #{attr_name}=(new_value)
|
176
|
-
self.original_#{attr_field} = ::Switchman::Shard.relative_id_for(new_value, ::Switchman::Shard.current(#{attr_connection_class}),
|
174
|
+
self.original_#{attr_field} = ::Switchman::Shard.relative_id_for(new_value, ::Switchman::Shard.current(#{attr_connection_class}), loaded_from_shard)
|
177
175
|
end
|
178
176
|
RUBY
|
179
177
|
end
|
@@ -251,7 +249,7 @@ module Switchman
|
|
251
249
|
|
252
250
|
def local_attribute(attr_name)
|
253
251
|
if self.class.sharded_column?(attr_name)
|
254
|
-
::Switchman::Shard.local_id_for(attribute(attr_name)
|
252
|
+
::Switchman::Shard.local_id_for(attribute(attr_name)).first
|
255
253
|
else
|
256
254
|
attribute(attr_name)
|
257
255
|
end
|
@@ -142,6 +142,8 @@ module Switchman
|
|
142
142
|
else
|
143
143
|
Shard.current(self.class.connection_class_for_self)
|
144
144
|
end
|
145
|
+
|
146
|
+
@loaded_from_shard ||= Shard.current(self.class.connection_class_for_self)
|
145
147
|
readonly! if shadow_record? && !Switchman.config[:writable_shadow_records]
|
146
148
|
super
|
147
149
|
end
|
@@ -169,27 +171,42 @@ module Switchman
|
|
169
171
|
end
|
170
172
|
end
|
171
173
|
|
174
|
+
# Returns "the shard that this record was actually loaded from" , as
|
175
|
+
# opposed to "the shard this record belongs on", which might be
|
176
|
+
# different if this is a shadow record.
|
177
|
+
def loaded_from_shard
|
178
|
+
@loaded_from_shard || fallback_shard
|
179
|
+
end
|
180
|
+
|
172
181
|
def shard
|
173
|
-
@shard ||
|
182
|
+
@shard || fallback_shard
|
174
183
|
end
|
175
184
|
|
176
185
|
def shard=(new_shard)
|
177
186
|
raise ::ActiveRecord::ReadOnlyRecord if !new_record? || @shard_set_in_stone
|
178
187
|
|
179
|
-
|
188
|
+
if shard == new_shard
|
189
|
+
@loaded_from_shard = new_shard
|
190
|
+
return
|
191
|
+
end
|
180
192
|
|
181
193
|
attributes.each do |attr, value|
|
182
194
|
self[attr] = Shard.relative_id_for(value, shard, new_shard) if self.class.sharded_column?(attr)
|
183
195
|
end
|
196
|
+
@loaded_from_shard = new_shard
|
184
197
|
@shard = new_shard
|
185
198
|
end
|
186
199
|
|
187
200
|
def save(*, **)
|
201
|
+
raise Errors::ManuallyCreatedShadowRecordError if creating_shadow_record?
|
202
|
+
|
188
203
|
@shard_set_in_stone = true
|
189
204
|
super
|
190
205
|
end
|
191
206
|
|
192
207
|
def save!(*, **)
|
208
|
+
raise Errors::ManuallyCreatedShadowRecordError if creating_shadow_record?
|
209
|
+
|
193
210
|
@shard_set_in_stone = true
|
194
211
|
super
|
195
212
|
end
|
@@ -221,7 +238,7 @@ module Switchman
|
|
221
238
|
end
|
222
239
|
|
223
240
|
def hash
|
224
|
-
self.class.sharded_primary_key? ? self.class
|
241
|
+
self.class.sharded_primary_key? ? [self.class, global_id].hash : super
|
225
242
|
end
|
226
243
|
|
227
244
|
def to_param
|
@@ -270,6 +287,16 @@ module Switchman
|
|
270
287
|
self.class.connection_class_for_self
|
271
288
|
end
|
272
289
|
end
|
290
|
+
|
291
|
+
private
|
292
|
+
|
293
|
+
def fallback_shard
|
294
|
+
Shard.current(self.class.connection_class_for_self) || Shard.default
|
295
|
+
end
|
296
|
+
|
297
|
+
def creating_shadow_record?
|
298
|
+
new_record? && shadow_record?
|
299
|
+
end
|
273
300
|
end
|
274
301
|
end
|
275
302
|
end
|
@@ -44,6 +44,23 @@ module Switchman
|
|
44
44
|
end
|
45
45
|
|
46
46
|
module MigrationContext
|
47
|
+
def migrate(...)
|
48
|
+
connection = ::ActiveRecord::Base.connection
|
49
|
+
connection_pool = ::ActiveRecord::Base.connection_pool
|
50
|
+
previous_schema_cache = connection_pool.get_schema_cache(connection)
|
51
|
+
temporary_schema_cache = ::ActiveRecord::ConnectionAdapters::SchemaCache.new(connection)
|
52
|
+
|
53
|
+
reset_column_information
|
54
|
+
connection_pool.set_schema_cache(temporary_schema_cache)
|
55
|
+
|
56
|
+
begin
|
57
|
+
super(...)
|
58
|
+
ensure
|
59
|
+
connection_pool.set_schema_cache(previous_schema_cache)
|
60
|
+
reset_column_information
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
47
64
|
def migrations
|
48
65
|
return @migrations if instance_variable_defined?(:@migrations)
|
49
66
|
|
@@ -51,6 +68,12 @@ module Switchman
|
|
51
68
|
key = Digest::MD5.hexdigest(migration_files.sort.join(','))
|
52
69
|
@migrations = migrations_cache[key] ||= super
|
53
70
|
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def reset_column_information
|
75
|
+
::ActiveRecord::Base.descendants.reject { |m| m <= UnshardedRecord }.each(&:reset_column_information)
|
76
|
+
end
|
54
77
|
end
|
55
78
|
end
|
56
79
|
end
|
@@ -16,6 +16,14 @@ module Switchman
|
|
16
16
|
db = shard.database_server
|
17
17
|
db.unguard { super }
|
18
18
|
end
|
19
|
+
|
20
|
+
def reload(*)
|
21
|
+
res = super
|
22
|
+
# When a shadow record is reloaded the real record is returned. So
|
23
|
+
# we need to ensure the loaded_from_shard is set correctly after a reload.
|
24
|
+
@loaded_from_shard = @shard
|
25
|
+
res
|
26
|
+
end
|
19
27
|
end
|
20
28
|
end
|
21
29
|
end
|
@@ -221,10 +221,8 @@ module Switchman
|
|
221
221
|
|
222
222
|
unless schema == false
|
223
223
|
shard.activate do
|
224
|
-
|
225
|
-
::ActiveRecord::Base.connection.
|
226
|
-
::ActiveRecord::Base.connection.migration_context.migrate
|
227
|
-
end
|
224
|
+
::ActiveRecord::Base.connection.transaction(requires_new: true) do
|
225
|
+
::ActiveRecord::Base.connection.migration_context.migrate
|
228
226
|
end
|
229
227
|
|
230
228
|
::ActiveRecord::Base.descendants.reject do |m|
|
@@ -240,7 +238,6 @@ module Switchman
|
|
240
238
|
rescue
|
241
239
|
shard&.destroy
|
242
240
|
shard&.drop_database rescue nil unless schema_already_existed
|
243
|
-
reset_column_information unless schema == false rescue nil
|
244
241
|
raise
|
245
242
|
ensure
|
246
243
|
self.class.creating_new_shard = false
|
@@ -272,26 +269,5 @@ module Switchman
|
|
272
269
|
end
|
273
270
|
@primary_shard
|
274
271
|
end
|
275
|
-
|
276
|
-
private
|
277
|
-
|
278
|
-
def reset_column_information
|
279
|
-
::ActiveRecord::Base.descendants.reject { |m| m <= UnshardedRecord }.each(&:reset_column_information)
|
280
|
-
end
|
281
|
-
|
282
|
-
def with_empty_caches
|
283
|
-
connection = ::ActiveRecord::Base.connection
|
284
|
-
connection_pool = ::ActiveRecord::Base.connection_pool
|
285
|
-
previous_schema_cache = connection_pool.get_schema_cache(connection)
|
286
|
-
temporary_schema_cache = ::ActiveRecord::ConnectionAdapters::SchemaCache.new(connection)
|
287
|
-
|
288
|
-
reset_column_information
|
289
|
-
connection_pool.set_schema_cache(temporary_schema_cache)
|
290
|
-
|
291
|
-
yield
|
292
|
-
|
293
|
-
connection_pool.set_schema_cache(previous_schema_cache)
|
294
|
-
reset_column_information
|
295
|
-
end
|
296
272
|
end
|
297
273
|
end
|
data/lib/switchman/errors.rb
CHANGED
@@ -5,5 +5,11 @@ module Switchman
|
|
5
5
|
class NonExistentShardError < RuntimeError; end
|
6
6
|
|
7
7
|
class ParallelShardExecError < RuntimeError; end
|
8
|
+
|
9
|
+
class ManuallyCreatedShadowRecordError < RuntimeError
|
10
|
+
def initialize(msg = "It looks like you're trying to manually create a shadow record. Please use Switchman::ActiveRecord::Base#save_shadow_record instead.")
|
11
|
+
super
|
12
|
+
end
|
13
|
+
end
|
8
14
|
end
|
9
15
|
end
|
data/lib/switchman/parallel.rb
CHANGED
data/lib/switchman/version.rb
CHANGED
data/lib/tasks/switchman.rake
CHANGED
@@ -3,9 +3,9 @@
|
|
3
3
|
# In rails 7.0+ if you have only 1 db in the env it doesn't try to do explicit activation
|
4
4
|
# (and for rails purposes we only have one db per env because each database server is a separate env)
|
5
5
|
if Rails.version < '7.0'
|
6
|
-
task_prefix =
|
7
|
-
|
8
|
-
|
6
|
+
task_prefix = Rake::Task.task_defined?('app:db:migrate') ? 'app:db' : 'db'
|
7
|
+
Rake::Task["#{task_prefix}:migrate"].clear_actions.enhance do
|
8
|
+
ActiveRecord::Tasks::DatabaseTasks.migrate
|
9
9
|
# Ensure this doesn't blow up when running inside the dummy app
|
10
10
|
Rake::Task["#{task_prefix}:_dump"].invoke
|
11
11
|
end
|
@@ -19,8 +19,8 @@ module Switchman
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def self.scope(base_scope = Shard,
|
22
|
-
database_server: ENV
|
23
|
-
shard: ENV
|
22
|
+
database_server: ENV.fetch('DATABASE_SERVER', nil),
|
23
|
+
shard: ENV.fetch('SHARD', nil))
|
24
24
|
servers = DatabaseServer.all
|
25
25
|
|
26
26
|
if database_server
|
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.
|
4
|
+
version: 3.4.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: 2023-03-
|
13
|
+
date: 2023-03-31 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activerecord
|
@@ -192,6 +192,20 @@ dependencies:
|
|
192
192
|
- - "~>"
|
193
193
|
- !ruby/object:Gem::Version
|
194
194
|
version: '1.10'
|
195
|
+
- !ruby/object:Gem::Dependency
|
196
|
+
name: rubocop-performance
|
197
|
+
requirement: !ruby/object:Gem::Requirement
|
198
|
+
requirements:
|
199
|
+
- - "~>"
|
200
|
+
- !ruby/object:Gem::Version
|
201
|
+
version: '1.16'
|
202
|
+
type: :development
|
203
|
+
prerelease: false
|
204
|
+
version_requirements: !ruby/object:Gem::Requirement
|
205
|
+
requirements:
|
206
|
+
- - "~>"
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: '1.16'
|
195
209
|
- !ruby/object:Gem::Dependency
|
196
210
|
name: rubocop-rake
|
197
211
|
requirement: !ruby/object:Gem::Requirement
|