switchman 3.0.6 → 3.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 +1 -1
- data/lib/switchman/action_controller/caching.rb +2 -2
- data/lib/switchman/active_record/abstract_adapter.rb +2 -4
- data/lib/switchman/active_record/associations.rb +223 -0
- data/lib/switchman/active_record/attribute_methods.rb +144 -84
- data/lib/switchman/active_record/base.rb +71 -31
- data/lib/switchman/active_record/calculations.rb +12 -5
- data/lib/switchman/active_record/connection_pool.rb +2 -4
- data/lib/switchman/active_record/database_configurations.rb +18 -2
- data/lib/switchman/active_record/finder_methods.rb +2 -2
- data/lib/switchman/active_record/model_schema.rb +1 -1
- data/lib/switchman/active_record/persistence.rb +3 -5
- data/lib/switchman/active_record/postgresql_adapter.rb +1 -1
- data/lib/switchman/active_record/query_methods.rb +23 -14
- data/lib/switchman/active_record/reflection.rb +1 -1
- data/lib/switchman/active_record/relation.rb +15 -18
- data/lib/switchman/active_record/statement_cache.rb +2 -2
- data/lib/switchman/active_record/table_definition.rb +1 -1
- data/lib/switchman/active_support/cache.rb +16 -0
- data/lib/switchman/arel.rb +28 -6
- data/lib/switchman/database_server.rb +28 -20
- data/lib/switchman/default_shard.rb +0 -2
- data/lib/switchman/engine.rb +63 -125
- data/lib/switchman/errors.rb +4 -2
- data/lib/switchman/guard_rail/relation.rb +6 -9
- data/lib/switchman/guard_rail.rb +5 -0
- data/lib/switchman/parallel.rb +68 -0
- data/lib/switchman/r_spec_helper.rb +8 -0
- data/lib/switchman/rails.rb +1 -4
- data/{app/models → lib}/switchman/shard.rb +30 -131
- data/lib/switchman/sharded_instrumenter.rb +1 -1
- data/lib/switchman/standard_error.rb +10 -11
- data/{app/models → lib}/switchman/unsharded_record.rb +1 -1
- data/lib/switchman/version.rb +1 -1
- data/lib/switchman.rb +22 -2
- data/lib/tasks/switchman.rake +16 -9
- metadata +19 -18
- data/lib/switchman/active_record/association.rb +0 -206
- data/lib/switchman/open4.rb +0 -80
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 48b27124d5233adf585aed105d0b2522c76249bdd1e13fbcfd7e904241d3a624
|
|
4
|
+
data.tar.gz: fea1ccc0ad63f00633fb90bee1d7bd4606f6de0e18564250865c1b1dde61ef8d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c05b445111dd0e4b16ecbfaea6ab84557fa2cbadc6102fa0af6fb43cff2f6bbb6606e825f76ed20f6b8a4a2a2966e31babadf66c5eb065a6c4d8a7eea6a8b92a
|
|
7
|
+
data.tar.gz: 0af9e15706142f90772d24e2187db93ec83331997bccbb25c1b89cec2dde1bd2414d2774fec3785c7bea1a1b06d3fa5ad49e15b716f0aa35794e2e5734c848a8
|
data/Rakefile
CHANGED
|
@@ -13,8 +13,8 @@ module Switchman
|
|
|
13
13
|
# disallow assigning to ActionController::Base.cache_store or
|
|
14
14
|
# ActionController::Base#cache_store for the same reasons we disallow
|
|
15
15
|
# assigning to Rails.cache
|
|
16
|
-
def cache_store=(
|
|
17
|
-
raise NoMethodError
|
|
16
|
+
def cache_store=(cache)
|
|
17
|
+
raise NoMethodError unless cache == ::Rails.cache
|
|
18
18
|
end
|
|
19
19
|
end
|
|
20
20
|
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'switchman/sharded_instrumenter'
|
|
4
|
-
|
|
5
3
|
module Switchman
|
|
6
4
|
module ActiveRecord
|
|
7
5
|
module AbstractAdapter
|
|
8
6
|
module ForeignKeyCheck
|
|
9
7
|
def add_column(table, name, type, limit: nil, **)
|
|
10
|
-
|
|
8
|
+
Switchman.foreign_key_check(name, type, limit: limit)
|
|
11
9
|
super
|
|
12
10
|
end
|
|
13
11
|
end
|
|
@@ -35,7 +33,7 @@ module Switchman
|
|
|
35
33
|
|
|
36
34
|
protected
|
|
37
35
|
|
|
38
|
-
def log(
|
|
36
|
+
def log(...)
|
|
39
37
|
super
|
|
40
38
|
ensure
|
|
41
39
|
@last_query_at = Time.now
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Switchman
|
|
4
|
+
module ActiveRecord
|
|
5
|
+
module Associations
|
|
6
|
+
module Association
|
|
7
|
+
def shard
|
|
8
|
+
reflection.shard(owner)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def build_record(*args)
|
|
12
|
+
shard.activate { super }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def load_target
|
|
16
|
+
shard.activate { super }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def scope
|
|
20
|
+
shard_value = @reflection.options[:multishard] ? @owner : shard
|
|
21
|
+
@owner.shard.activate { super.shard(shard_value, :association) }
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
module CollectionAssociation
|
|
26
|
+
def find_target
|
|
27
|
+
shards = reflection.options[:multishard] && owner.respond_to?(:associated_shards) ? owner.associated_shards : [shard]
|
|
28
|
+
# activate both the owner and the target's shard category, so that Reflection#join_id_for,
|
|
29
|
+
# when called for the owner, will be returned relative to shard the query will execute on
|
|
30
|
+
Shard.with_each_shard(shards, [klass.connection_class_for_self, owner.class.connection_class_for_self].uniq) do
|
|
31
|
+
super
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def _create_record(*)
|
|
36
|
+
shard.activate { super }
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
module BelongsToAssociation
|
|
41
|
+
def replace_keys(record, force: false)
|
|
42
|
+
if record&.class&.sharded_column?(reflection.association_primary_key(record.class))
|
|
43
|
+
foreign_id = record[reflection.association_primary_key(record.class)]
|
|
44
|
+
owner[reflection.foreign_key] = Shard.relative_id_for(foreign_id, record.shard, owner.shard)
|
|
45
|
+
else
|
|
46
|
+
super
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def shard
|
|
51
|
+
if @owner.class.sharded_column?(@reflection.foreign_key) &&
|
|
52
|
+
(foreign_id = @owner[@reflection.foreign_key])
|
|
53
|
+
Shard.shard_for(foreign_id, @owner.shard)
|
|
54
|
+
else
|
|
55
|
+
super
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
module ForeignAssociation
|
|
61
|
+
# significant change:
|
|
62
|
+
# * transpose the key to the correct shard
|
|
63
|
+
def set_owner_attributes(record) # rubocop:disable Naming/AccessorMethodName
|
|
64
|
+
return if options[:through]
|
|
65
|
+
|
|
66
|
+
key = owner._read_attribute(reflection.join_foreign_key)
|
|
67
|
+
key = Shard.relative_id_for(key, owner.shard, shard)
|
|
68
|
+
record._write_attribute(reflection.join_primary_key, key)
|
|
69
|
+
|
|
70
|
+
record._write_attribute(reflection.type, owner.class.polymorphic_name) if reflection.type
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
module Extension
|
|
75
|
+
def self.build(_model, _reflection); end
|
|
76
|
+
|
|
77
|
+
def self.valid_options
|
|
78
|
+
[:multishard]
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
::ActiveRecord::Associations::Builder::Association.extensions << Extension
|
|
83
|
+
|
|
84
|
+
module Preloader
|
|
85
|
+
module Association
|
|
86
|
+
module LoaderQuery
|
|
87
|
+
def load_records_in_batch(loaders)
|
|
88
|
+
# While in theory loading multiple associations that end up being effectively the same would be nice
|
|
89
|
+
# it's not very switchman compatible, so just don't bother trying to use that logic
|
|
90
|
+
# raw_records = records_for(loaders)
|
|
91
|
+
|
|
92
|
+
loaders.each do |loader|
|
|
93
|
+
loader.load_records(nil)
|
|
94
|
+
loader.run
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Copypasta from Activerecord but with added global_id_for goodness.
|
|
100
|
+
def records_for(ids)
|
|
101
|
+
scope.where(association_key_name => ids).load do |record|
|
|
102
|
+
global_key = if model.connection_class_for_self == UnshardedRecord
|
|
103
|
+
convert_key(record[association_key_name])
|
|
104
|
+
else
|
|
105
|
+
Shard.global_id_for(record[association_key_name], record.shard)
|
|
106
|
+
end
|
|
107
|
+
owner = owners_by_key[convert_key(global_key)].first
|
|
108
|
+
association = owner.association(reflection.name)
|
|
109
|
+
association.set_inverse_instance(record)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# significant changes:
|
|
114
|
+
# * partition_by_shard the records_for call
|
|
115
|
+
# * re-globalize the fetched owner id before looking up in the map
|
|
116
|
+
# TODO: the ignored param currently loads records; we should probably not waste effort double-loading them
|
|
117
|
+
# Change introduced here: https://github.com/rails/rails/commit/c6c0b2e8af64509b699b782aadfecaa430700ece
|
|
118
|
+
def load_records(raw_records = nil)
|
|
119
|
+
# owners can be duplicated when a relation has a collection association join
|
|
120
|
+
# #compare_by_identity makes such owners different hash keys
|
|
121
|
+
@records_by_owner = {}.compare_by_identity
|
|
122
|
+
|
|
123
|
+
if ::Rails.version < '7.0' && owner_keys.empty?
|
|
124
|
+
raw_records ||= []
|
|
125
|
+
else
|
|
126
|
+
# determine the shard to search for each owner
|
|
127
|
+
if reflection.macro == :belongs_to
|
|
128
|
+
# for belongs_to, it's the shard of the foreign_key
|
|
129
|
+
partition_proc = lambda do |owner|
|
|
130
|
+
if owner.class.sharded_column?(owner_key_name)
|
|
131
|
+
Shard.shard_for(owner[owner_key_name], owner.shard)
|
|
132
|
+
else
|
|
133
|
+
Shard.current
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
elsif !reflection.options[:multishard]
|
|
137
|
+
# for non-multishard associations, it's *just* the owner's shard
|
|
138
|
+
partition_proc = ->(owner) { owner.shard }
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
raw_records ||= Shard.partition_by_shard(owners, partition_proc) do |partitioned_owners|
|
|
142
|
+
relative_owner_keys = partitioned_owners.map do |owner|
|
|
143
|
+
key = owner[owner_key_name]
|
|
144
|
+
if key && owner.class.sharded_column?(owner_key_name)
|
|
145
|
+
key = Shard.relative_id_for(key, owner.shard,
|
|
146
|
+
Shard.current(klass.connection_class_for_self))
|
|
147
|
+
end
|
|
148
|
+
convert_key(key)
|
|
149
|
+
end
|
|
150
|
+
relative_owner_keys.compact!
|
|
151
|
+
relative_owner_keys.uniq!
|
|
152
|
+
records_for(relative_owner_keys)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
@preloaded_records = raw_records.select do |record|
|
|
157
|
+
assignments = false
|
|
158
|
+
|
|
159
|
+
owner_key = record[association_key_name]
|
|
160
|
+
if owner_key && record.class.sharded_column?(association_key_name)
|
|
161
|
+
owner_key = Shard.global_id_for(owner_key,
|
|
162
|
+
record.shard)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
owners_by_key[convert_key(owner_key)].each do |owner|
|
|
166
|
+
entries = (@records_by_owner[owner] ||= [])
|
|
167
|
+
|
|
168
|
+
if reflection.collection? || entries.empty?
|
|
169
|
+
entries << record
|
|
170
|
+
assignments = true
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
assignments
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# significant change: globalize keys on sharded columns
|
|
179
|
+
def owners_by_key
|
|
180
|
+
@owners_by_key ||= owners.each_with_object({}) do |owner, result|
|
|
181
|
+
key = owner[owner_key_name]
|
|
182
|
+
key = Shard.global_id_for(key, owner.shard) if key && owner.class.sharded_column?(owner_key_name)
|
|
183
|
+
key = convert_key(key)
|
|
184
|
+
(result[key] ||= []) << owner if key
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# significant change: don't cache scope (since it could be for different shards)
|
|
189
|
+
def scope
|
|
190
|
+
build_scope
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
module CollectionProxy
|
|
196
|
+
def initialize(*args)
|
|
197
|
+
super
|
|
198
|
+
self.shard_value = scope.shard_value
|
|
199
|
+
self.shard_source_value = :association
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def shard(*args)
|
|
203
|
+
scope.shard(*args)
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
module AutosaveAssociation
|
|
208
|
+
def record_changed?(reflection, record, key)
|
|
209
|
+
record.new_record? ||
|
|
210
|
+
(record.has_attribute?(reflection.foreign_key) && record.send(reflection.foreign_key) != key) || # have to use send instead of [] because sharding
|
|
211
|
+
record.attribute_changed?(reflection.foreign_key)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def save_belongs_to_association(reflection)
|
|
215
|
+
# this seems counter-intuitive, but the autosave code will assign to attribute bypassing switchman,
|
|
216
|
+
# after reading the id attribute _without_ bypassing switchman. So we need Shard.current for the
|
|
217
|
+
# category of the associated record to match Shard.current for the category of self
|
|
218
|
+
shard.activate(connection_class_for_self_for_reflection(reflection)) { super }
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
@@ -25,6 +25,16 @@ module Switchman
|
|
|
25
25
|
@sharded_column_values[column_name]
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
+
def define_attribute_methods
|
|
29
|
+
super
|
|
30
|
+
# ensure that we're using the sharded attribute method
|
|
31
|
+
# and not the silly one in AR::AttributeMethods::PrimaryKey
|
|
32
|
+
return unless sharded_column?(@primary_key)
|
|
33
|
+
|
|
34
|
+
class_eval(build_sharded_getter('id', '_read_attribute(@primary_key)', "::#{connection_class_for_self.name}"), __FILE__, __LINE__)
|
|
35
|
+
class_eval(build_sharded_setter('id', @primary_key, "::#{connection_class_for_self.name}"), __FILE__, __LINE__)
|
|
36
|
+
end
|
|
37
|
+
|
|
28
38
|
protected
|
|
29
39
|
|
|
30
40
|
def reflection_for_integer_attribute(attr_name)
|
|
@@ -36,17 +46,30 @@ module Switchman
|
|
|
36
46
|
raise if connection.open_transactions.positive?
|
|
37
47
|
end
|
|
38
48
|
|
|
49
|
+
# rubocop:disable Naming/MethodParameterName
|
|
50
|
+
def define_cached_method(owner, name, namespace:, as:, &block)
|
|
51
|
+
if ::Rails.version < '7.0'
|
|
52
|
+
yield owner
|
|
53
|
+
owner.rename_method(as, name)
|
|
54
|
+
else
|
|
55
|
+
owner.define_cached_method(name, namespace: namespace, as: as, &block)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
# rubocop:enable Naming/MethodParameterName
|
|
59
|
+
|
|
39
60
|
def define_method_global_attribute(attr_name, owner:)
|
|
40
61
|
if sharded_column?(attr_name)
|
|
41
|
-
owner
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
62
|
+
define_cached_method(owner, "global_#{attr_name}", as: "sharded_global_#{attr_name}", namespace: :switchman) do |batch|
|
|
63
|
+
batch << <<-RUBY
|
|
64
|
+
def sharded_global_#{attr_name}
|
|
65
|
+
raw_value = original_#{attr_name}
|
|
66
|
+
return nil if raw_value.nil?
|
|
67
|
+
return raw_value if raw_value > ::Switchman::Shard::IDS_PER_SHARD
|
|
68
|
+
|
|
69
|
+
::Switchman::Shard.global_id_for(raw_value, shard)
|
|
70
|
+
end
|
|
71
|
+
RUBY
|
|
72
|
+
end
|
|
50
73
|
else
|
|
51
74
|
define_method_unsharded_column(attr_name, 'global', owner)
|
|
52
75
|
end
|
|
@@ -54,113 +77,150 @@ module Switchman
|
|
|
54
77
|
|
|
55
78
|
def define_method_local_attribute(attr_name, owner:)
|
|
56
79
|
if sharded_column?(attr_name)
|
|
57
|
-
owner
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
80
|
+
define_cached_method(owner, "local_#{attr_name}", as: "sharded_local_#{attr_name}", namespace: :switchman) do |batch|
|
|
81
|
+
batch << <<-RUBY
|
|
82
|
+
def sharded_local_#{attr_name}
|
|
83
|
+
raw_value = original_#{attr_name}
|
|
84
|
+
return nil if raw_value.nil?
|
|
85
|
+
return raw_value % ::Switchman::Shard::IDS_PER_SHARD
|
|
86
|
+
end
|
|
87
|
+
RUBY
|
|
88
|
+
end
|
|
64
89
|
else
|
|
65
90
|
define_method_unsharded_column(attr_name, 'local', owner)
|
|
66
91
|
end
|
|
67
92
|
end
|
|
68
93
|
|
|
69
|
-
# see also Base#
|
|
94
|
+
# see also Base#connection_class_for_self_for_reflection
|
|
70
95
|
# the difference being this will output static strings for the common cases, making them
|
|
71
96
|
# more performant
|
|
72
|
-
def
|
|
97
|
+
def connection_class_for_self_code_for_reflection(reflection)
|
|
73
98
|
if reflection
|
|
74
99
|
if reflection.options[:polymorphic]
|
|
75
100
|
# a polymorphic association has to be discovered at runtime. This code ends up being something like
|
|
76
|
-
# context_type.&.constantize&.
|
|
77
|
-
"read_attribute(:#{reflection.foreign_type})&.constantize&.
|
|
101
|
+
# context_type.&.constantize&.connection_class_for_self
|
|
102
|
+
"read_attribute(:#{reflection.foreign_type})&.constantize&.connection_class_for_self"
|
|
78
103
|
else
|
|
79
104
|
# otherwise we can just return a symbol for the statically known type of the association
|
|
80
|
-
"::#{reflection.klass.
|
|
105
|
+
"::#{reflection.klass.connection_class_for_self.name}"
|
|
81
106
|
end
|
|
82
107
|
else
|
|
83
|
-
"::#{
|
|
108
|
+
"::#{connection_class_for_self.name}"
|
|
84
109
|
end
|
|
85
110
|
end
|
|
86
111
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
112
|
+
def define_method_attribute(attr_name, owner:)
|
|
113
|
+
if sharded_column?(attr_name)
|
|
114
|
+
reflection = reflection_for_integer_attribute(attr_name)
|
|
115
|
+
class_name = connection_class_for_self_code_for_reflection(reflection)
|
|
116
|
+
safe_class_name = class_name.unpack1('h*')
|
|
117
|
+
define_cached_method(owner, attr_name, as: "sharded_#{safe_class_name}_#{attr_name}", namespace: :switchman) do |batch|
|
|
118
|
+
batch << build_sharded_getter("sharded_#{safe_class_name}_#{attr_name}", "original_#{attr_name}", class_name)
|
|
119
|
+
end
|
|
120
|
+
else
|
|
121
|
+
define_cached_method(owner, attr_name, as: "plain_#{attr_name}", namespace: :switchman) do |batch|
|
|
122
|
+
batch << <<-RUBY
|
|
123
|
+
def plain_#{attr_name}
|
|
124
|
+
_read_attribute("#{attr_name}") { |n| missing_attribute(n, caller) }
|
|
125
|
+
end
|
|
126
|
+
RUBY
|
|
127
|
+
end
|
|
92
128
|
end
|
|
129
|
+
end
|
|
93
130
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
131
|
+
def build_sharded_getter(attr_name, raw_expr, attr_connection_class)
|
|
132
|
+
<<-RUBY
|
|
133
|
+
def #{attr_name}
|
|
134
|
+
raw_value = #{raw_expr}
|
|
135
|
+
return nil if raw_value.nil?
|
|
136
|
+
|
|
137
|
+
abs_raw_value = raw_value.abs
|
|
138
|
+
current_shard = ::Switchman::Shard.current(#{attr_connection_class})
|
|
139
|
+
same_shard = shard == current_shard
|
|
140
|
+
return raw_value if same_shard && abs_raw_value < ::Switchman::Shard::IDS_PER_SHARD
|
|
141
|
+
|
|
142
|
+
value_shard_id = abs_raw_value / ::Switchman::Shard::IDS_PER_SHARD
|
|
143
|
+
# this is a stupid case when someone stuffed a global id for the current shard in instead
|
|
144
|
+
# of a local id
|
|
145
|
+
return raw_value % ::Switchman::Shard::IDS_PER_SHARD if value_shard_id == current_shard.id
|
|
146
|
+
return raw_value if !same_shard && abs_raw_value > ::Switchman::Shard::IDS_PER_SHARD
|
|
147
|
+
return shard.global_id_for(raw_value) if !same_shard && abs_raw_value < ::Switchman::Shard::IDS_PER_SHARD
|
|
148
|
+
|
|
149
|
+
::Switchman::Shard.relative_id_for(raw_value, shard, current_shard)
|
|
150
|
+
end
|
|
151
|
+
RUBY
|
|
97
152
|
end
|
|
98
153
|
|
|
99
|
-
def
|
|
154
|
+
def define_method_attribute=(attr_name, owner:)
|
|
100
155
|
if sharded_column?(attr_name)
|
|
101
156
|
reflection = reflection_for_integer_attribute(attr_name)
|
|
102
|
-
|
|
103
|
-
|
|
157
|
+
class_name = connection_class_for_self_code_for_reflection(reflection)
|
|
158
|
+
safe_class_name = class_name.unpack1('h*')
|
|
159
|
+
define_cached_method(owner, "#{attr_name}=", as: "sharded_#{safe_class_name}_#{attr_name}=", namespace: :switchman) do |batch|
|
|
160
|
+
batch << build_sharded_setter("sharded_#{safe_class_name}_#{attr_name}", attr_name, class_name)
|
|
161
|
+
end
|
|
162
|
+
else
|
|
163
|
+
define_cached_method(owner, "#{attr_name}=", as: "plain_#{attr_name}=", namespace: :switchman) do |batch|
|
|
164
|
+
batch << <<-RUBY
|
|
165
|
+
def plain_#{attr_name}=(new_value)
|
|
166
|
+
_write_attribute('#{attr_name}', new_value)
|
|
167
|
+
end
|
|
168
|
+
RUBY
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
104
172
|
|
|
105
|
-
|
|
173
|
+
def build_sharded_setter(attr_name, attr_field, attr_connection_class)
|
|
174
|
+
<<-RUBY
|
|
175
|
+
def #{attr_name}=(new_value)
|
|
176
|
+
self.original_#{attr_field} = ::Switchman::Shard.relative_id_for(new_value, ::Switchman::Shard.current(#{attr_connection_class}), shard)
|
|
106
177
|
end
|
|
178
|
+
RUBY
|
|
179
|
+
end
|
|
107
180
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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)
|
|
129
|
-
end
|
|
181
|
+
def define_method_original_attribute(attr_name, owner:)
|
|
182
|
+
if sharded_column?(attr_name)
|
|
183
|
+
define_cached_method(owner, "original_#{attr_name}", as: "sharded_original_#{attr_name}", namespace: :switchman) do |batch|
|
|
184
|
+
batch << <<-RUBY
|
|
185
|
+
def sharded_original_#{attr_name}
|
|
186
|
+
_read_attribute("#{attr_name}") { |n| missing_attribute(n, caller) }
|
|
187
|
+
end
|
|
188
|
+
RUBY
|
|
189
|
+
end
|
|
190
|
+
else
|
|
191
|
+
define_method_unsharded_column(attr_name, 'global', owner)
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def define_method_original_attribute=(attr_name, owner:)
|
|
196
|
+
return unless sharded_column?(attr_name)
|
|
130
197
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
198
|
+
define_cached_method(owner, "original_#{attr_name}=", as: "sharded_original_#{attr_name}=", namespace: :switchman) do |batch|
|
|
199
|
+
batch << <<-RUBY
|
|
200
|
+
def sharded_original_#{attr_name}=(new_value)
|
|
201
|
+
_write_attribute('#{attr_name}', new_value)
|
|
134
202
|
end
|
|
135
203
|
RUBY
|
|
136
|
-
else
|
|
137
|
-
define_method_unsharded_column(attr_name, 'global', owner)
|
|
138
204
|
end
|
|
139
205
|
end
|
|
140
206
|
|
|
141
207
|
def define_method_unsharded_column(attr_name, prefix, owner)
|
|
142
|
-
return if columns_hash["#{prefix}_#{attr_name}"]
|
|
208
|
+
return if columns_hash["#{prefix}_#{attr_name}"] || attr_name == 'id'
|
|
143
209
|
|
|
144
|
-
owner
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
210
|
+
define_cached_method(owner, "#{prefix}_#{attr_name}", as: "unsharded_#{prefix}_#{attr_name}", namespace: :switchman) do |batch|
|
|
211
|
+
batch << <<-RUBY
|
|
212
|
+
def unsharded_#{prefix}_#{attr_name}
|
|
213
|
+
raise NoMethodError, "undefined method `#{prefix}_#{attr_name}'; are you missing an association?"
|
|
214
|
+
end
|
|
215
|
+
RUBY
|
|
216
|
+
end
|
|
149
217
|
end
|
|
150
218
|
end
|
|
151
219
|
|
|
152
|
-
def self.
|
|
153
|
-
klass.singleton_class.
|
|
220
|
+
def self.prepended(klass)
|
|
221
|
+
klass.singleton_class.prepend(ClassMethods)
|
|
154
222
|
klass.attribute_method_prefix 'global_', 'local_', 'original_'
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
# ensure that we're using the sharded attribute method
|
|
158
|
-
# and not the silly one in AR::AttributeMethods::PrimaryKey
|
|
159
|
-
def id
|
|
160
|
-
return super if is_a?(Shard)
|
|
161
|
-
|
|
162
|
-
self.class.define_attribute_methods
|
|
163
|
-
super
|
|
223
|
+
klass.attribute_method_affix prefix: 'original_', suffix: '='
|
|
164
224
|
end
|
|
165
225
|
|
|
166
226
|
# these are called if the specific methods haven't been defined yet
|
|
@@ -168,7 +228,7 @@ module Switchman
|
|
|
168
228
|
return super unless self.class.sharded_column?(attr_name)
|
|
169
229
|
|
|
170
230
|
reflection = self.class.send(:reflection_for_integer_attribute, attr_name)
|
|
171
|
-
::Switchman::Shard.relative_id_for(super, shard, ::Switchman::Shard.current(
|
|
231
|
+
::Switchman::Shard.relative_id_for(super, shard, ::Switchman::Shard.current(connection_class_for_self_for_reflection(reflection)))
|
|
172
232
|
end
|
|
173
233
|
|
|
174
234
|
def attribute=(attr_name, new_value)
|
|
@@ -178,7 +238,7 @@ module Switchman
|
|
|
178
238
|
end
|
|
179
239
|
|
|
180
240
|
reflection = self.class.send(:reflection_for_integer_attribute, attr_name)
|
|
181
|
-
super(::Switchman::Shard.relative_id_for(new_value, ::Switchman::Shard.current(
|
|
241
|
+
super(::Switchman::Shard.relative_id_for(new_value, ::Switchman::Shard.current(connection_class_for_self_for_reflection(reflection)), shard))
|
|
182
242
|
end
|
|
183
243
|
|
|
184
244
|
def global_attribute(attr_name)
|
|
@@ -199,15 +259,15 @@ module Switchman
|
|
|
199
259
|
|
|
200
260
|
private
|
|
201
261
|
|
|
202
|
-
def
|
|
262
|
+
def connection_class_for_self_for_reflection(reflection)
|
|
203
263
|
if reflection
|
|
204
264
|
if reflection.options[:polymorphic]
|
|
205
|
-
read_attribute(reflection.foreign_type)&.constantize&.
|
|
265
|
+
read_attribute(reflection.foreign_type)&.constantize&.connection_class_for_self
|
|
206
266
|
else
|
|
207
|
-
reflection.klass.
|
|
267
|
+
reflection.klass.connection_class_for_self
|
|
208
268
|
end
|
|
209
269
|
else
|
|
210
|
-
self.class.
|
|
270
|
+
self.class.connection_class_for_self
|
|
211
271
|
end
|
|
212
272
|
end
|
|
213
273
|
end
|