switchman 3.3.1 → 4.1.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/Rakefile +15 -14
- data/db/migrate/20180828183945_add_default_shard_index.rb +1 -1
- data/db/migrate/20190114212900_add_unique_name_indexes.rb +10 -4
- data/lib/switchman/active_record/abstract_adapter.rb +5 -3
- data/lib/switchman/active_record/associations.rb +91 -51
- data/lib/switchman/active_record/attribute_methods.rb +88 -43
- data/lib/switchman/active_record/base.rb +113 -40
- data/lib/switchman/active_record/calculations.rb +98 -51
- data/lib/switchman/active_record/connection_handler.rb +18 -0
- data/lib/switchman/active_record/connection_pool.rb +56 -6
- data/lib/switchman/active_record/database_configurations.rb +37 -15
- data/lib/switchman/active_record/finder_methods.rb +47 -17
- data/lib/switchman/active_record/log_subscriber.rb +11 -5
- data/lib/switchman/active_record/migration.rb +51 -3
- data/lib/switchman/active_record/pending_migration_connection.rb +17 -0
- data/lib/switchman/active_record/persistence.rb +30 -0
- data/lib/switchman/active_record/postgresql_adapter.rb +37 -22
- data/lib/switchman/active_record/predicate_builder.rb +2 -2
- data/lib/switchman/active_record/query_cache.rb +57 -20
- data/lib/switchman/active_record/query_methods.rb +148 -44
- data/lib/switchman/active_record/reflection.rb +9 -2
- data/lib/switchman/active_record/relation.rb +79 -15
- data/lib/switchman/active_record/spawn_methods.rb +3 -7
- data/lib/switchman/active_record/statement_cache.rb +2 -2
- data/lib/switchman/active_record/table_definition.rb +1 -1
- data/lib/switchman/active_record/tasks/database_tasks.rb +6 -1
- data/lib/switchman/active_record/test_fixtures.rb +75 -25
- data/lib/switchman/active_support/cache.rb +9 -4
- data/lib/switchman/arel.rb +34 -18
- data/lib/switchman/call_super.rb +2 -8
- data/lib/switchman/database_server.rb +72 -34
- data/lib/switchman/default_shard.rb +14 -3
- data/lib/switchman/engine.rb +38 -22
- data/lib/switchman/environment.rb +2 -2
- data/lib/switchman/errors.rb +13 -0
- data/lib/switchman/guard_rail/relation.rb +1 -2
- data/lib/switchman/parallel.rb +6 -6
- data/lib/switchman/r_spec_helper.rb +12 -11
- data/lib/switchman/shard.rb +185 -71
- data/lib/switchman/sharded_instrumenter.rb +3 -3
- data/lib/switchman/shared_schema_cache.rb +11 -0
- data/lib/switchman/standard_error.rb +4 -0
- data/lib/switchman/test_helper.rb +3 -3
- data/lib/switchman/version.rb +1 -1
- data/lib/switchman.rb +27 -15
- data/lib/tasks/switchman.rake +96 -60
- metadata +50 -46
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e03940e1a5f3d0e7b933d99b3cbd68e3998b55f14ac0e7eec91ef81a193445e1
|
|
4
|
+
data.tar.gz: 923793689418332ab6ce2ffef2981567bd74b6358916794d958172638b0ec160
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6950ac12cf6532f98e3674ded8890e7bb2f9ef50f33657220096de50508715ab047250c163546dc7396c61d15197ee2b517ae342f84cbe007ae22a217ed7751f
|
|
7
|
+
data.tar.gz: c45df067998ec5868bfe2c6a0f52608ffd9de9a19e2ce55ba55fc9986e077833df9964c5783c0f394a9683cb21a610e7f2a781d88e690fbc2bfeef5e8e352826
|
data/Rakefile
CHANGED
|
@@ -2,37 +2,38 @@
|
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
begin
|
|
5
|
-
require
|
|
5
|
+
require "bundler/setup"
|
|
6
6
|
rescue LoadError
|
|
7
|
-
puts
|
|
7
|
+
puts "You must `gem install bundler` and `bundle install` to run rake tasks"
|
|
8
8
|
end
|
|
9
9
|
begin
|
|
10
|
-
require
|
|
10
|
+
require "rdoc/task"
|
|
11
11
|
rescue LoadError
|
|
12
|
-
require
|
|
13
|
-
require
|
|
12
|
+
require "rdoc/rdoc"
|
|
13
|
+
require "rake/rdoctask"
|
|
14
14
|
RDoc::Task = Rake::RDocTask
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
RDoc::Task.new(:rdoc) do |rdoc|
|
|
18
|
-
rdoc.rdoc_dir =
|
|
19
|
-
rdoc.title =
|
|
20
|
-
rdoc.options <<
|
|
21
|
-
rdoc.rdoc_files.include(
|
|
18
|
+
rdoc.rdoc_dir = "rdoc"
|
|
19
|
+
rdoc.title = "Switchman"
|
|
20
|
+
rdoc.options << "--line-numbers"
|
|
21
|
+
rdoc.rdoc_files.include("lib/**/*.rb")
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
load "./spec/tasks/coverage.rake"
|
|
25
|
+
APP_RAKEFILE = File.expand_path("spec/dummy/Rakefile", __dir__)
|
|
26
|
+
load "rails/tasks/engine.rake"
|
|
26
27
|
|
|
27
28
|
Bundler::GemHelper.install_tasks
|
|
28
29
|
|
|
29
|
-
require
|
|
30
|
+
require "rspec/core/rake_task"
|
|
30
31
|
RSpec::Core::RakeTask.new
|
|
31
32
|
|
|
32
|
-
require
|
|
33
|
+
require "rubocop/rake_task"
|
|
33
34
|
|
|
34
35
|
RuboCop::RakeTask.new do |task|
|
|
35
|
-
task.options = [
|
|
36
|
+
task.options = ["-S"]
|
|
36
37
|
end
|
|
37
38
|
|
|
38
39
|
task default: %i[spec]
|
|
@@ -5,7 +5,7 @@ class AddDefaultShardIndex < ActiveRecord::Migration[4.2]
|
|
|
5
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
|
-
options = if connection.adapter_name ==
|
|
8
|
+
options = if connection.adapter_name == "PostgreSQL"
|
|
9
9
|
{ unique: true, where: '"default"' }
|
|
10
10
|
else
|
|
11
11
|
{}
|
|
@@ -3,9 +3,15 @@
|
|
|
3
3
|
class AddUniqueNameIndexes < ActiveRecord::Migration[4.2]
|
|
4
4
|
def change
|
|
5
5
|
add_index :switchman_shards, %i[database_server_id name], unique: true
|
|
6
|
-
add_index :switchman_shards,
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
add_index :switchman_shards,
|
|
7
|
+
:database_server_id,
|
|
8
|
+
unique: true,
|
|
9
|
+
where: "name IS NULL",
|
|
10
|
+
name: "index_switchman_shards_unique_primary_shard"
|
|
11
|
+
add_index :switchman_shards,
|
|
12
|
+
"(true)",
|
|
13
|
+
unique: true,
|
|
14
|
+
where: "database_server_id IS NULL AND name IS NULL",
|
|
15
|
+
name: "index_switchman_shards_unique_primary_db_and_shard"
|
|
10
16
|
end
|
|
11
17
|
end
|
|
@@ -5,7 +5,7 @@ module Switchman
|
|
|
5
5
|
module AbstractAdapter
|
|
6
6
|
module ForeignKeyCheck
|
|
7
7
|
def add_column(table, name, type, limit: nil, **)
|
|
8
|
-
Switchman.foreign_key_check(name, type, limit:
|
|
8
|
+
Switchman.foreign_key_check(name, type, limit:)
|
|
9
9
|
super
|
|
10
10
|
end
|
|
11
11
|
end
|
|
@@ -27,8 +27,10 @@ module Switchman
|
|
|
27
27
|
quote_table_name(name)
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
if ::Rails.version < "7.1"
|
|
31
|
+
def schema_migration
|
|
32
|
+
::ActiveRecord::SchemaMigration
|
|
33
|
+
end
|
|
32
34
|
end
|
|
33
35
|
|
|
34
36
|
protected
|
|
@@ -24,11 +24,23 @@ module Switchman
|
|
|
24
24
|
|
|
25
25
|
module CollectionAssociation
|
|
26
26
|
def find_target
|
|
27
|
-
shards = reflection.options[:multishard] && owner.respond_to?(:associated_shards)
|
|
27
|
+
shards = if reflection.options[:multishard] && owner.respond_to?(:associated_shards)
|
|
28
|
+
owner.associated_shards
|
|
29
|
+
else
|
|
30
|
+
[shard]
|
|
31
|
+
end
|
|
28
32
|
# activate both the owner and the target's shard category, so that Reflection#join_id_for,
|
|
29
33
|
# when called for the owner, will be returned relative to shard the query will execute on
|
|
30
|
-
Shard.with_each_shard(shards,
|
|
31
|
-
|
|
34
|
+
Shard.with_each_shard(shards,
|
|
35
|
+
[klass.connection_class_for_self, owner.class.connection_class_for_self].uniq) do
|
|
36
|
+
if reflection.options[:multishard] && owner.respond_to?(:associated_shards) && reflection.has_scope?
|
|
37
|
+
# Prevent duplicate results when reflection has a scope (when it would use the skip_statement_cache? path)
|
|
38
|
+
# otherwise, the super call will set the shard_value to the object, causing it to iterate too many times
|
|
39
|
+
# over the associated shards
|
|
40
|
+
scope.shard(Shard.current(scope.klass.connection_class_for_self), :association).to_a
|
|
41
|
+
else
|
|
42
|
+
super
|
|
43
|
+
end
|
|
32
44
|
end
|
|
33
45
|
end
|
|
34
46
|
|
|
@@ -50,7 +62,7 @@ module Switchman
|
|
|
50
62
|
def shard
|
|
51
63
|
if @owner.class.sharded_column?(@reflection.foreign_key) &&
|
|
52
64
|
(foreign_id = @owner[@reflection.foreign_key])
|
|
53
|
-
Shard.shard_for(foreign_id, @owner.
|
|
65
|
+
Shard.shard_for(foreign_id, @owner.loaded_from_shard)
|
|
54
66
|
else
|
|
55
67
|
super
|
|
56
68
|
end
|
|
@@ -128,7 +140,12 @@ module Switchman
|
|
|
128
140
|
Shard.lookup(shard).activate do
|
|
129
141
|
scope_was = loader_query.scope
|
|
130
142
|
begin
|
|
131
|
-
loader_query.instance_variable_set(
|
|
143
|
+
loader_query.instance_variable_set(
|
|
144
|
+
:@scope,
|
|
145
|
+
loader_query.scope.shard(
|
|
146
|
+
Shard.current(loader_query.scope.model.connection_class_for_self)
|
|
147
|
+
)
|
|
148
|
+
)
|
|
132
149
|
ret += loader_query.load_records_for_keys(keys) do |record|
|
|
133
150
|
loaders.each { |l| l.set_inverse(record) }
|
|
134
151
|
end
|
|
@@ -157,7 +174,7 @@ module Switchman
|
|
|
157
174
|
end
|
|
158
175
|
|
|
159
176
|
# Disabling to keep closer to rails original
|
|
160
|
-
# rubocop:disable Naming/AccessorMethodName
|
|
177
|
+
# rubocop:disable Naming/AccessorMethodName
|
|
161
178
|
# significant changes:
|
|
162
179
|
# * globalize the key to lookup
|
|
163
180
|
def set_inverse(record)
|
|
@@ -174,7 +191,7 @@ module Switchman
|
|
|
174
191
|
association.set_inverse_instance(record)
|
|
175
192
|
end
|
|
176
193
|
end
|
|
177
|
-
# rubocop:enable Naming/AccessorMethodName
|
|
194
|
+
# rubocop:enable Naming/AccessorMethodName
|
|
178
195
|
|
|
179
196
|
# significant changes:
|
|
180
197
|
# * partition_by_shard the records_for call
|
|
@@ -186,40 +203,7 @@ module Switchman
|
|
|
186
203
|
# #compare_by_identity makes such owners different hash keys
|
|
187
204
|
@records_by_owner = {}.compare_by_identity
|
|
188
205
|
|
|
189
|
-
|
|
190
|
-
raw_records ||= loader_query.records_for([self])
|
|
191
|
-
elsif owner_keys.empty?
|
|
192
|
-
raw_records ||= []
|
|
193
|
-
else
|
|
194
|
-
# determine the shard to search for each owner
|
|
195
|
-
if reflection.macro == :belongs_to
|
|
196
|
-
# for belongs_to, it's the shard of the foreign_key
|
|
197
|
-
partition_proc = lambda do |owner|
|
|
198
|
-
if owner.class.sharded_column?(owner_key_name)
|
|
199
|
-
Shard.shard_for(owner[owner_key_name], owner.shard)
|
|
200
|
-
else
|
|
201
|
-
Shard.current
|
|
202
|
-
end
|
|
203
|
-
end
|
|
204
|
-
elsif !reflection.options[:multishard]
|
|
205
|
-
# for non-multishard associations, it's *just* the owner's shard
|
|
206
|
-
partition_proc = ->(owner) { owner.shard }
|
|
207
|
-
end
|
|
208
|
-
|
|
209
|
-
raw_records ||= Shard.partition_by_shard(owners, partition_proc) do |partitioned_owners|
|
|
210
|
-
relative_owner_keys = partitioned_owners.map do |owner|
|
|
211
|
-
key = owner[owner_key_name]
|
|
212
|
-
if key && owner.class.sharded_column?(owner_key_name)
|
|
213
|
-
key = Shard.relative_id_for(key, owner.shard,
|
|
214
|
-
Shard.current(klass.connection_class_for_self))
|
|
215
|
-
end
|
|
216
|
-
convert_key(key)
|
|
217
|
-
end
|
|
218
|
-
relative_owner_keys.compact!
|
|
219
|
-
relative_owner_keys.uniq!
|
|
220
|
-
records_for(relative_owner_keys)
|
|
221
|
-
end
|
|
222
|
-
end
|
|
206
|
+
raw_records ||= loader_query.records_for([self])
|
|
223
207
|
|
|
224
208
|
@preloaded_records = raw_records.select do |record|
|
|
225
209
|
assignments = false
|
|
@@ -273,17 +257,73 @@ module Switchman
|
|
|
273
257
|
end
|
|
274
258
|
|
|
275
259
|
module AutosaveAssociation
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
260
|
+
if ::Rails.version < "7.1"
|
|
261
|
+
def association_foreign_key_changed?(reflection, record, key)
|
|
262
|
+
return false if reflection.through_reflection?
|
|
263
|
+
|
|
264
|
+
# have to use send instead of _read_attribute because sharding
|
|
265
|
+
record.has_attribute?(reflection.foreign_key) && record.send(reflection.foreign_key) != key
|
|
266
|
+
end
|
|
281
267
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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?
|
|
277
|
+
|
|
278
|
+
foreign_key = Array(reflection.foreign_key)
|
|
279
|
+
return false unless foreign_key.all? { |k| record._has_attribute?(k) }
|
|
280
|
+
|
|
281
|
+
# have to use send instead of _read_attribute because sharding
|
|
282
|
+
foreign_key.map { |k| record.send(k) } != Array(key)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def save_belongs_to_association(reflection)
|
|
286
|
+
association = association_instance_get(reflection.name)
|
|
287
|
+
return unless association&.loaded? && !association.stale_target?
|
|
288
|
+
|
|
289
|
+
record = association.load_target
|
|
290
|
+
return unless record && !record.destroyed?
|
|
291
|
+
|
|
292
|
+
autosave = reflection.options[:autosave]
|
|
293
|
+
|
|
294
|
+
if autosave && record.marked_for_destruction?
|
|
295
|
+
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
|
+
|
|
303
|
+
if association.updated?
|
|
304
|
+
primary_key = Array(compute_primary_key(reflection, record)).map(&:to_s)
|
|
305
|
+
foreign_key = Array(reflection.foreign_key)
|
|
306
|
+
|
|
307
|
+
primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
|
|
308
|
+
primary_key_foreign_key_pairs.each do |pk, fk|
|
|
309
|
+
# Notable change: add relative_id_for here
|
|
310
|
+
association_id = if record.class.sharded_column?(pk)
|
|
311
|
+
Shard.relative_id_for(
|
|
312
|
+
record._read_attribute(pk),
|
|
313
|
+
record.shard,
|
|
314
|
+
shard
|
|
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!
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
saved if autosave
|
|
325
|
+
end
|
|
326
|
+
end
|
|
287
327
|
end
|
|
288
328
|
end
|
|
289
329
|
end
|
|
@@ -26,13 +26,22 @@ module Switchman
|
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def define_attribute_methods
|
|
29
|
-
super
|
|
29
|
+
result = super
|
|
30
30
|
# ensure that we're using the sharded attribute method
|
|
31
31
|
# and not the silly one in AR::AttributeMethods::PrimaryKey
|
|
32
|
-
return unless sharded_column?(@primary_key)
|
|
33
|
-
|
|
34
|
-
class_eval(
|
|
35
|
-
|
|
32
|
+
return result unless sharded_column?(@primary_key)
|
|
33
|
+
|
|
34
|
+
class_eval(
|
|
35
|
+
build_sharded_getter("id",
|
|
36
|
+
"_read_attribute(@primary_key)",
|
|
37
|
+
"::#{connection_class_for_self.name}"),
|
|
38
|
+
__FILE__,
|
|
39
|
+
__LINE__
|
|
40
|
+
)
|
|
41
|
+
class_eval(build_sharded_setter("id", @primary_key, "::#{connection_class_for_self.name}"),
|
|
42
|
+
__FILE__,
|
|
43
|
+
__LINE__)
|
|
44
|
+
result
|
|
36
45
|
end
|
|
37
46
|
|
|
38
47
|
protected
|
|
@@ -46,20 +55,22 @@ module Switchman
|
|
|
46
55
|
raise if connection.open_transactions.positive?
|
|
47
56
|
end
|
|
48
57
|
|
|
49
|
-
# rubocop:disable Naming/MethodParameterName
|
|
50
58
|
def define_cached_method(owner, name, namespace:, as:, &block)
|
|
51
|
-
if ::Rails.version <
|
|
52
|
-
|
|
53
|
-
|
|
59
|
+
if ::Rails.version < "7.1.4"
|
|
60
|
+
# https://github.com/rails/rails/commit/a2a12fc2e3f4e6d06f81d4c74c88f8e6b3369ee6#diff-5b59ece6d9396b596f06271cec0ea726e3360911383511c49b1a66f454bfc2b6L30
|
|
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, &block)
|
|
54
63
|
else
|
|
55
|
-
owner.define_cached_method(name, namespace
|
|
64
|
+
owner.define_cached_method(name, namespace:, as:, &block)
|
|
56
65
|
end
|
|
57
66
|
end
|
|
58
|
-
# rubocop:enable Naming/MethodParameterName
|
|
59
67
|
|
|
60
|
-
def define_method_global_attribute(attr_name, owner:)
|
|
68
|
+
def define_method_global_attribute(attr_name, owner:, as: attr_name)
|
|
61
69
|
if sharded_column?(attr_name)
|
|
62
|
-
define_cached_method(owner,
|
|
70
|
+
define_cached_method(owner,
|
|
71
|
+
"sharded_global_#{attr_name}",
|
|
72
|
+
as: "global_#{as}",
|
|
73
|
+
namespace: :switchman) do |batch|
|
|
63
74
|
batch << <<-RUBY
|
|
64
75
|
def sharded_global_#{attr_name}
|
|
65
76
|
raw_value = original_#{attr_name}
|
|
@@ -71,13 +82,16 @@ module Switchman
|
|
|
71
82
|
RUBY
|
|
72
83
|
end
|
|
73
84
|
else
|
|
74
|
-
define_method_unsharded_column(attr_name,
|
|
85
|
+
define_method_unsharded_column(attr_name, "global", owner)
|
|
75
86
|
end
|
|
76
87
|
end
|
|
77
88
|
|
|
78
|
-
def define_method_local_attribute(attr_name, owner:)
|
|
89
|
+
def define_method_local_attribute(attr_name, owner:, as: attr_name)
|
|
79
90
|
if sharded_column?(attr_name)
|
|
80
|
-
define_cached_method(owner,
|
|
91
|
+
define_cached_method(owner,
|
|
92
|
+
"sharded_local_#{attr_name}",
|
|
93
|
+
as: "local_#{as}",
|
|
94
|
+
namespace: :switchman) do |batch|
|
|
81
95
|
batch << <<-RUBY
|
|
82
96
|
def sharded_local_#{attr_name}
|
|
83
97
|
raw_value = original_#{attr_name}
|
|
@@ -87,7 +101,7 @@ module Switchman
|
|
|
87
101
|
RUBY
|
|
88
102
|
end
|
|
89
103
|
else
|
|
90
|
-
define_method_unsharded_column(attr_name,
|
|
104
|
+
define_method_unsharded_column(attr_name, "local", owner)
|
|
91
105
|
end
|
|
92
106
|
end
|
|
93
107
|
|
|
@@ -99,7 +113,13 @@ module Switchman
|
|
|
99
113
|
if reflection.options[:polymorphic]
|
|
100
114
|
# a polymorphic association has to be discovered at runtime. This code ends up being something like
|
|
101
115
|
# context_type.&.constantize&.connection_class_for_self
|
|
102
|
-
|
|
116
|
+
<<~RUBY
|
|
117
|
+
begin
|
|
118
|
+
read_attribute(:#{reflection.foreign_type})&.constantize&.connection_class_for_self
|
|
119
|
+
rescue NameError
|
|
120
|
+
::ActiveRecord::Base
|
|
121
|
+
end
|
|
122
|
+
RUBY
|
|
103
123
|
else
|
|
104
124
|
# otherwise we can just return a symbol for the statically known type of the association
|
|
105
125
|
"::#{reflection.klass.connection_class_for_self.name}"
|
|
@@ -109,16 +129,21 @@ module Switchman
|
|
|
109
129
|
end
|
|
110
130
|
end
|
|
111
131
|
|
|
112
|
-
def define_method_attribute(attr_name, owner:)
|
|
132
|
+
def define_method_attribute(attr_name, owner:, as: attr_name)
|
|
113
133
|
if sharded_column?(attr_name)
|
|
114
134
|
reflection = reflection_for_integer_attribute(attr_name)
|
|
115
135
|
class_name = connection_class_for_self_code_for_reflection(reflection)
|
|
116
|
-
safe_class_name = class_name.unpack1(
|
|
117
|
-
define_cached_method(owner,
|
|
118
|
-
|
|
136
|
+
safe_class_name = class_name.unpack1("h*")
|
|
137
|
+
define_cached_method(owner,
|
|
138
|
+
"sharded_#{safe_class_name}_#{attr_name}",
|
|
139
|
+
as:,
|
|
140
|
+
namespace: :switchman) do |batch|
|
|
141
|
+
batch << build_sharded_getter("sharded_#{safe_class_name}_#{attr_name}",
|
|
142
|
+
"original_#{as}",
|
|
143
|
+
class_name)
|
|
119
144
|
end
|
|
120
145
|
else
|
|
121
|
-
define_cached_method(owner,
|
|
146
|
+
define_cached_method(owner, "plain_#{attr_name}", as:, namespace: :switchman) do |batch|
|
|
122
147
|
batch << <<-RUBY
|
|
123
148
|
def plain_#{attr_name}
|
|
124
149
|
_read_attribute("#{attr_name}") { |n| missing_attribute(n, caller) }
|
|
@@ -136,7 +161,7 @@ module Switchman
|
|
|
136
161
|
|
|
137
162
|
abs_raw_value = raw_value.abs
|
|
138
163
|
current_shard = ::Switchman::Shard.current(#{attr_connection_class})
|
|
139
|
-
same_shard =
|
|
164
|
+
same_shard = loaded_from_shard == current_shard
|
|
140
165
|
return raw_value if same_shard && abs_raw_value < ::Switchman::Shard::IDS_PER_SHARD
|
|
141
166
|
|
|
142
167
|
value_shard_id = abs_raw_value / ::Switchman::Shard::IDS_PER_SHARD
|
|
@@ -144,23 +169,26 @@ module Switchman
|
|
|
144
169
|
# of a local id
|
|
145
170
|
return raw_value % ::Switchman::Shard::IDS_PER_SHARD if value_shard_id == current_shard.id
|
|
146
171
|
return raw_value if !same_shard && abs_raw_value > ::Switchman::Shard::IDS_PER_SHARD
|
|
147
|
-
return
|
|
172
|
+
return loaded_from_shard.global_id_for(raw_value) if !same_shard && abs_raw_value < ::Switchman::Shard::IDS_PER_SHARD
|
|
148
173
|
|
|
149
|
-
::Switchman::Shard.relative_id_for(raw_value,
|
|
174
|
+
::Switchman::Shard.relative_id_for(raw_value, loaded_from_shard, current_shard)
|
|
150
175
|
end
|
|
151
176
|
RUBY
|
|
152
177
|
end
|
|
153
178
|
|
|
154
|
-
def define_method_attribute=(attr_name, owner:)
|
|
179
|
+
def define_method_attribute=(attr_name, owner:, as: attr_name)
|
|
155
180
|
if sharded_column?(attr_name)
|
|
156
181
|
reflection = reflection_for_integer_attribute(attr_name)
|
|
157
182
|
class_name = connection_class_for_self_code_for_reflection(reflection)
|
|
158
|
-
safe_class_name = class_name.unpack1(
|
|
159
|
-
define_cached_method(owner,
|
|
183
|
+
safe_class_name = class_name.unpack1("h*")
|
|
184
|
+
define_cached_method(owner,
|
|
185
|
+
"sharded_#{safe_class_name}_#{attr_name}=",
|
|
186
|
+
as: "#{as}=",
|
|
187
|
+
namespace: :switchman) do |batch|
|
|
160
188
|
batch << build_sharded_setter("sharded_#{safe_class_name}_#{attr_name}", attr_name, class_name)
|
|
161
189
|
end
|
|
162
190
|
else
|
|
163
|
-
define_cached_method(owner, "#{attr_name}=", as: "
|
|
191
|
+
define_cached_method(owner, "plain_#{attr_name}=", as: "#{as}=", namespace: :switchman) do |batch|
|
|
164
192
|
batch << <<-RUBY
|
|
165
193
|
def plain_#{attr_name}=(new_value)
|
|
166
194
|
_write_attribute('#{attr_name}', new_value)
|
|
@@ -173,14 +201,17 @@ module Switchman
|
|
|
173
201
|
def build_sharded_setter(attr_name, attr_field, attr_connection_class)
|
|
174
202
|
<<-RUBY
|
|
175
203
|
def #{attr_name}=(new_value)
|
|
176
|
-
self.original_#{attr_field} = ::Switchman::Shard.relative_id_for(new_value, ::Switchman::Shard.current(#{attr_connection_class}),
|
|
204
|
+
self.original_#{attr_field} = ::Switchman::Shard.relative_id_for(new_value, ::Switchman::Shard.current(#{attr_connection_class}), loaded_from_shard)
|
|
177
205
|
end
|
|
178
206
|
RUBY
|
|
179
207
|
end
|
|
180
208
|
|
|
181
|
-
def define_method_original_attribute(attr_name, owner:)
|
|
209
|
+
def define_method_original_attribute(attr_name, owner:, as: attr_name)
|
|
182
210
|
if sharded_column?(attr_name)
|
|
183
|
-
define_cached_method(owner,
|
|
211
|
+
define_cached_method(owner,
|
|
212
|
+
"sharded_original_#{attr_name}",
|
|
213
|
+
as: "original_#{as}",
|
|
214
|
+
namespace: :switchman) do |batch|
|
|
184
215
|
batch << <<-RUBY
|
|
185
216
|
def sharded_original_#{attr_name}
|
|
186
217
|
_read_attribute("#{attr_name}") { |n| missing_attribute(n, caller) }
|
|
@@ -188,14 +219,17 @@ module Switchman
|
|
|
188
219
|
RUBY
|
|
189
220
|
end
|
|
190
221
|
else
|
|
191
|
-
define_method_unsharded_column(attr_name,
|
|
222
|
+
define_method_unsharded_column(attr_name, "global", owner)
|
|
192
223
|
end
|
|
193
224
|
end
|
|
194
225
|
|
|
195
|
-
def define_method_original_attribute=(attr_name, owner:)
|
|
226
|
+
def define_method_original_attribute=(attr_name, owner:, as: attr_name)
|
|
196
227
|
return unless sharded_column?(attr_name)
|
|
197
228
|
|
|
198
|
-
define_cached_method(owner,
|
|
229
|
+
define_cached_method(owner,
|
|
230
|
+
"sharded_original_#{attr_name}=",
|
|
231
|
+
as: "original_#{as}=",
|
|
232
|
+
namespace: :switchman) do |batch|
|
|
199
233
|
batch << <<-RUBY
|
|
200
234
|
def sharded_original_#{attr_name}=(new_value)
|
|
201
235
|
_write_attribute('#{attr_name}', new_value)
|
|
@@ -205,9 +239,12 @@ module Switchman
|
|
|
205
239
|
end
|
|
206
240
|
|
|
207
241
|
def define_method_unsharded_column(attr_name, prefix, owner)
|
|
208
|
-
return if columns_hash["#{prefix}_#{attr_name}"] || attr_name ==
|
|
242
|
+
return if columns_hash["#{prefix}_#{attr_name}"] || attr_name == "id"
|
|
209
243
|
|
|
210
|
-
define_cached_method(owner,
|
|
244
|
+
define_cached_method(owner,
|
|
245
|
+
"unsharded_#{prefix}_#{attr_name}",
|
|
246
|
+
as: "#{prefix}_#{attr_name}",
|
|
247
|
+
namespace: :switchman) do |batch|
|
|
211
248
|
batch << <<-RUBY
|
|
212
249
|
def unsharded_#{prefix}_#{attr_name}
|
|
213
250
|
raise NoMethodError, "undefined method `#{prefix}_#{attr_name}'; are you missing an association?"
|
|
@@ -219,8 +256,8 @@ module Switchman
|
|
|
219
256
|
|
|
220
257
|
def self.prepended(klass)
|
|
221
258
|
klass.singleton_class.prepend(ClassMethods)
|
|
222
|
-
klass.attribute_method_prefix
|
|
223
|
-
klass.attribute_method_affix prefix:
|
|
259
|
+
klass.attribute_method_prefix "global_", "local_", "original_"
|
|
260
|
+
klass.attribute_method_affix prefix: "original_", suffix: "="
|
|
224
261
|
end
|
|
225
262
|
|
|
226
263
|
# these are called if the specific methods haven't been defined yet
|
|
@@ -228,7 +265,11 @@ module Switchman
|
|
|
228
265
|
return super unless self.class.sharded_column?(attr_name)
|
|
229
266
|
|
|
230
267
|
reflection = self.class.send(:reflection_for_integer_attribute, attr_name)
|
|
231
|
-
::Switchman::Shard.relative_id_for(
|
|
268
|
+
::Switchman::Shard.relative_id_for(
|
|
269
|
+
super,
|
|
270
|
+
shard,
|
|
271
|
+
::Switchman::Shard.current(connection_class_for_self_for_reflection(reflection))
|
|
272
|
+
)
|
|
232
273
|
end
|
|
233
274
|
|
|
234
275
|
def attribute=(attr_name, new_value)
|
|
@@ -238,7 +279,11 @@ module Switchman
|
|
|
238
279
|
end
|
|
239
280
|
|
|
240
281
|
reflection = self.class.send(:reflection_for_integer_attribute, attr_name)
|
|
241
|
-
super(
|
|
282
|
+
super(attr_name, ::Switchman::Shard.relative_id_for(
|
|
283
|
+
new_value,
|
|
284
|
+
::Switchman::Shard.current(connection_class_for_self_for_reflection(reflection)),
|
|
285
|
+
shard
|
|
286
|
+
))
|
|
242
287
|
end
|
|
243
288
|
|
|
244
289
|
def global_attribute(attr_name)
|
|
@@ -251,7 +296,7 @@ module Switchman
|
|
|
251
296
|
|
|
252
297
|
def local_attribute(attr_name)
|
|
253
298
|
if self.class.sharded_column?(attr_name)
|
|
254
|
-
::Switchman::Shard.local_id_for(attribute(attr_name)
|
|
299
|
+
::Switchman::Shard.local_id_for(attribute(attr_name)).first
|
|
255
300
|
else
|
|
256
301
|
attribute(attr_name)
|
|
257
302
|
end
|