switchman 3.3.6 → 3.4.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/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
|