switchman 3.0.24 → 4.2.4
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 +16 -15
- 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 +11 -7
- data/lib/switchman/active_record/associations.rb +157 -50
- data/lib/switchman/active_record/attribute_methods.rb +192 -101
- data/lib/switchman/active_record/base.rb +136 -33
- data/lib/switchman/active_record/calculations.rb +91 -48
- data/lib/switchman/active_record/connection_handler.rb +18 -0
- data/lib/switchman/active_record/connection_pool.rb +41 -6
- data/lib/switchman/active_record/database_configurations.rb +23 -13
- data/lib/switchman/active_record/finder_methods.rb +22 -16
- data/lib/switchman/active_record/log_subscriber.rb +3 -6
- data/lib/switchman/active_record/migration.rb +42 -17
- data/lib/switchman/active_record/model_schema.rb +1 -1
- data/lib/switchman/active_record/pending_migration_connection.rb +17 -0
- data/lib/switchman/active_record/persistence.rb +32 -2
- 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 +26 -17
- data/lib/switchman/active_record/query_methods.rb +249 -142
- data/lib/switchman/active_record/reflection.rb +10 -3
- data/lib/switchman/active_record/relation.rb +103 -32
- data/lib/switchman/active_record/spawn_methods.rb +3 -7
- data/lib/switchman/active_record/statement_cache.rb +13 -9
- 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 +71 -25
- data/lib/switchman/active_support/cache.rb +9 -4
- data/lib/switchman/arel.rb +16 -25
- data/lib/switchman/call_super.rb +2 -2
- data/lib/switchman/database_server.rb +68 -34
- data/lib/switchman/default_shard.rb +14 -3
- data/lib/switchman/engine.rb +36 -19
- data/lib/switchman/environment.rb +2 -2
- data/lib/switchman/errors.rb +13 -0
- data/lib/switchman/guard_rail/relation.rb +3 -3
- data/lib/switchman/parallel.rb +6 -6
- data/lib/switchman/r_spec_helper.rb +12 -11
- data/lib/switchman/shard.rb +182 -72
- data/lib/switchman/sharded_instrumenter.rb +9 -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 +18 -168
|
@@ -25,6 +25,25 @@ module Switchman
|
|
|
25
25
|
@sharded_column_values[column_name]
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
+
def define_attribute_methods
|
|
29
|
+
result = super
|
|
30
|
+
# ensure that we're using the sharded attribute method
|
|
31
|
+
# and not the silly one in AR::AttributeMethods::PrimaryKey
|
|
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
|
|
45
|
+
end
|
|
46
|
+
|
|
28
47
|
protected
|
|
29
48
|
|
|
30
49
|
def reflection_for_integer_attribute(attr_name)
|
|
@@ -36,131 +55,209 @@ module Switchman
|
|
|
36
55
|
raise if connection.open_transactions.positive?
|
|
37
56
|
end
|
|
38
57
|
|
|
39
|
-
def
|
|
40
|
-
if
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
58
|
+
def define_cached_method(owner, name, namespace:, as:, &)
|
|
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, &)
|
|
63
|
+
else
|
|
64
|
+
owner.define_cached_method(name, namespace:, as:, &)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
46
67
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
68
|
+
def define_method_global_attribute(attr_name, owner:, as: attr_name)
|
|
69
|
+
if sharded_column?(attr_name)
|
|
70
|
+
define_cached_method(owner,
|
|
71
|
+
"sharded_global_#{attr_name}",
|
|
72
|
+
as: "global_#{as}",
|
|
73
|
+
namespace: :switchman) do |batch|
|
|
74
|
+
batch << <<-RUBY
|
|
75
|
+
def sharded_global_#{attr_name}
|
|
76
|
+
raw_value = original_#{attr_name}
|
|
77
|
+
return nil if raw_value.nil?
|
|
78
|
+
return raw_value if raw_value > ::Switchman::Shard::IDS_PER_SHARD
|
|
79
|
+
|
|
80
|
+
::Switchman::Shard.global_id_for(raw_value, shard)
|
|
81
|
+
end
|
|
82
|
+
RUBY
|
|
83
|
+
end
|
|
50
84
|
else
|
|
51
|
-
define_method_unsharded_column(attr_name,
|
|
85
|
+
define_method_unsharded_column(attr_name, "global", owner)
|
|
52
86
|
end
|
|
53
87
|
end
|
|
54
88
|
|
|
55
|
-
def define_method_local_attribute(attr_name, owner:)
|
|
89
|
+
def define_method_local_attribute(attr_name, owner:, as: attr_name)
|
|
56
90
|
if sharded_column?(attr_name)
|
|
57
|
-
owner
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
91
|
+
define_cached_method(owner,
|
|
92
|
+
"sharded_local_#{attr_name}",
|
|
93
|
+
as: "local_#{as}",
|
|
94
|
+
namespace: :switchman) do |batch|
|
|
95
|
+
batch << <<-RUBY
|
|
96
|
+
def sharded_local_#{attr_name}
|
|
97
|
+
raw_value = original_#{attr_name}
|
|
98
|
+
return nil if raw_value.nil?
|
|
99
|
+
return raw_value % ::Switchman::Shard::IDS_PER_SHARD
|
|
100
|
+
end
|
|
101
|
+
RUBY
|
|
102
|
+
end
|
|
64
103
|
else
|
|
65
|
-
define_method_unsharded_column(attr_name,
|
|
104
|
+
define_method_unsharded_column(attr_name, "local", owner)
|
|
66
105
|
end
|
|
67
106
|
end
|
|
68
107
|
|
|
69
|
-
# see also Base#
|
|
108
|
+
# see also Base#connection_class_for_self_for_reflection
|
|
70
109
|
# the difference being this will output static strings for the common cases, making them
|
|
71
110
|
# more performant
|
|
72
|
-
def
|
|
111
|
+
def connection_class_for_self_code_for_reflection(reflection)
|
|
73
112
|
if reflection
|
|
74
113
|
if reflection.options[:polymorphic]
|
|
75
114
|
# a polymorphic association has to be discovered at runtime. This code ends up being something like
|
|
76
|
-
# context_type.&.constantize&.
|
|
77
|
-
|
|
115
|
+
# context_type.&.constantize&.connection_class_for_self
|
|
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
|
|
78
123
|
else
|
|
79
124
|
# otherwise we can just return a symbol for the statically known type of the association
|
|
80
|
-
"::#{reflection.klass.
|
|
125
|
+
"::#{reflection.klass.connection_class_for_self.name}"
|
|
81
126
|
end
|
|
82
127
|
else
|
|
83
|
-
"::#{
|
|
128
|
+
"::#{connection_class_for_self.name}"
|
|
84
129
|
end
|
|
85
130
|
end
|
|
86
131
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
132
|
+
def define_method_attribute(attr_name, owner:, as: attr_name)
|
|
133
|
+
if sharded_column?(attr_name)
|
|
134
|
+
reflection = reflection_for_integer_attribute(attr_name)
|
|
135
|
+
class_name = connection_class_for_self_code_for_reflection(reflection)
|
|
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)
|
|
144
|
+
end
|
|
145
|
+
else
|
|
146
|
+
define_cached_method(owner, "plain_#{attr_name}", as:, namespace: :switchman) do |batch|
|
|
147
|
+
batch << <<-RUBY
|
|
148
|
+
def plain_#{attr_name}
|
|
149
|
+
_read_attribute("#{attr_name}") { |n| missing_attribute(n, caller) }
|
|
150
|
+
end
|
|
151
|
+
RUBY
|
|
152
|
+
end
|
|
92
153
|
end
|
|
154
|
+
end
|
|
93
155
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
156
|
+
def build_sharded_getter(attr_name, raw_expr, attr_connection_class)
|
|
157
|
+
<<-RUBY
|
|
158
|
+
def #{attr_name}
|
|
159
|
+
raw_value = #{raw_expr}
|
|
160
|
+
return nil if raw_value.nil?
|
|
161
|
+
|
|
162
|
+
abs_raw_value = raw_value.abs
|
|
163
|
+
current_shard = ::Switchman::Shard.current(#{attr_connection_class})
|
|
164
|
+
same_shard = loaded_from_shard == current_shard
|
|
165
|
+
return raw_value if same_shard && abs_raw_value < ::Switchman::Shard::IDS_PER_SHARD
|
|
166
|
+
|
|
167
|
+
value_shard_id = abs_raw_value / ::Switchman::Shard::IDS_PER_SHARD
|
|
168
|
+
# this is a stupid case when someone stuffed a global id for the current shard in instead
|
|
169
|
+
# of a local id
|
|
170
|
+
return raw_value % ::Switchman::Shard::IDS_PER_SHARD if value_shard_id == current_shard.id
|
|
171
|
+
return raw_value if !same_shard && abs_raw_value > ::Switchman::Shard::IDS_PER_SHARD
|
|
172
|
+
return loaded_from_shard.global_id_for(raw_value) if !same_shard && abs_raw_value < ::Switchman::Shard::IDS_PER_SHARD
|
|
173
|
+
|
|
174
|
+
::Switchman::Shard.relative_id_for(raw_value, loaded_from_shard, current_shard)
|
|
175
|
+
end
|
|
176
|
+
RUBY
|
|
97
177
|
end
|
|
98
178
|
|
|
99
|
-
def
|
|
179
|
+
def define_method_attribute=(attr_name, owner:, as: attr_name)
|
|
100
180
|
if sharded_column?(attr_name)
|
|
101
181
|
reflection = reflection_for_integer_attribute(attr_name)
|
|
102
|
-
|
|
103
|
-
|
|
182
|
+
class_name = connection_class_for_self_code_for_reflection(reflection)
|
|
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|
|
|
188
|
+
batch << build_sharded_setter("sharded_#{safe_class_name}_#{attr_name}", attr_name, class_name)
|
|
189
|
+
end
|
|
190
|
+
else
|
|
191
|
+
define_cached_method(owner, "plain_#{attr_name}=", as: "#{as}=", namespace: :switchman) do |batch|
|
|
192
|
+
batch << <<-RUBY
|
|
193
|
+
def plain_#{attr_name}=(new_value)
|
|
194
|
+
_write_attribute('#{attr_name}', new_value)
|
|
195
|
+
end
|
|
196
|
+
RUBY
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
104
200
|
|
|
105
|
-
|
|
201
|
+
def build_sharded_setter(attr_name, attr_field, attr_connection_class)
|
|
202
|
+
<<-RUBY
|
|
203
|
+
def #{attr_name}=(new_value)
|
|
204
|
+
self.original_#{attr_field} = ::Switchman::Shard.relative_id_for(new_value, ::Switchman::Shard.current(#{attr_connection_class}), loaded_from_shard)
|
|
106
205
|
end
|
|
206
|
+
RUBY
|
|
207
|
+
end
|
|
107
208
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
::Switchman::Shard.relative_id_for(raw_value, shard, current_shard)
|
|
129
|
-
end
|
|
209
|
+
def define_method_original_attribute(attr_name, owner:, as: attr_name)
|
|
210
|
+
if sharded_column?(attr_name)
|
|
211
|
+
define_cached_method(owner,
|
|
212
|
+
"sharded_original_#{attr_name}",
|
|
213
|
+
as: "original_#{as}",
|
|
214
|
+
namespace: :switchman) do |batch|
|
|
215
|
+
batch << <<-RUBY
|
|
216
|
+
def sharded_original_#{attr_name}
|
|
217
|
+
_read_attribute("#{attr_name}") { |n| missing_attribute(n, caller) }
|
|
218
|
+
end
|
|
219
|
+
RUBY
|
|
220
|
+
end
|
|
221
|
+
else
|
|
222
|
+
define_method_unsharded_column(attr_name, "global", owner)
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def define_method_original_attribute=(attr_name, owner:, as: attr_name)
|
|
227
|
+
return unless sharded_column?(attr_name)
|
|
130
228
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
229
|
+
define_cached_method(owner,
|
|
230
|
+
"sharded_original_#{attr_name}=",
|
|
231
|
+
as: "original_#{as}=",
|
|
232
|
+
namespace: :switchman) do |batch|
|
|
233
|
+
batch << <<-RUBY
|
|
234
|
+
def sharded_original_#{attr_name}=(new_value)
|
|
235
|
+
_write_attribute('#{attr_name}', new_value)
|
|
134
236
|
end
|
|
135
237
|
RUBY
|
|
136
|
-
else
|
|
137
|
-
define_method_unsharded_column(attr_name, 'global', owner)
|
|
138
238
|
end
|
|
139
239
|
end
|
|
140
240
|
|
|
141
241
|
def define_method_unsharded_column(attr_name, prefix, owner)
|
|
142
|
-
return if columns_hash["#{prefix}_#{attr_name}"]
|
|
143
|
-
|
|
144
|
-
owner
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
242
|
+
return if columns_hash["#{prefix}_#{attr_name}"] || attr_name == "id"
|
|
243
|
+
|
|
244
|
+
define_cached_method(owner,
|
|
245
|
+
"unsharded_#{prefix}_#{attr_name}",
|
|
246
|
+
as: "#{prefix}_#{attr_name}",
|
|
247
|
+
namespace: :switchman) do |batch|
|
|
248
|
+
batch << <<-RUBY
|
|
249
|
+
def unsharded_#{prefix}_#{attr_name}
|
|
250
|
+
raise NoMethodError, "undefined method `#{prefix}_#{attr_name}'; are you missing an association?"
|
|
251
|
+
end
|
|
252
|
+
RUBY
|
|
253
|
+
end
|
|
149
254
|
end
|
|
150
255
|
end
|
|
151
256
|
|
|
152
|
-
def self.
|
|
153
|
-
klass.singleton_class.
|
|
154
|
-
klass.attribute_method_prefix
|
|
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
|
|
257
|
+
def self.prepended(klass)
|
|
258
|
+
klass.singleton_class.prepend(ClassMethods)
|
|
259
|
+
klass.attribute_method_prefix "global_", "local_", "original_"
|
|
260
|
+
klass.attribute_method_affix prefix: "original_", suffix: "="
|
|
164
261
|
end
|
|
165
262
|
|
|
166
263
|
# these are called if the specific methods haven't been defined yet
|
|
@@ -168,7 +265,11 @@ module Switchman
|
|
|
168
265
|
return super unless self.class.sharded_column?(attr_name)
|
|
169
266
|
|
|
170
267
|
reflection = self.class.send(:reflection_for_integer_attribute, attr_name)
|
|
171
|
-
::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
|
+
)
|
|
172
273
|
end
|
|
173
274
|
|
|
174
275
|
def attribute=(attr_name, new_value)
|
|
@@ -178,7 +279,11 @@ module Switchman
|
|
|
178
279
|
end
|
|
179
280
|
|
|
180
281
|
reflection = self.class.send(:reflection_for_integer_attribute, attr_name)
|
|
181
|
-
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
|
+
))
|
|
182
287
|
end
|
|
183
288
|
|
|
184
289
|
def global_attribute(attr_name)
|
|
@@ -191,25 +296,11 @@ module Switchman
|
|
|
191
296
|
|
|
192
297
|
def local_attribute(attr_name)
|
|
193
298
|
if self.class.sharded_column?(attr_name)
|
|
194
|
-
::Switchman::Shard.local_id_for(attribute(attr_name)
|
|
299
|
+
::Switchman::Shard.local_id_for(attribute(attr_name)).first
|
|
195
300
|
else
|
|
196
301
|
attribute(attr_name)
|
|
197
302
|
end
|
|
198
303
|
end
|
|
199
|
-
|
|
200
|
-
private
|
|
201
|
-
|
|
202
|
-
def connection_classes_for_reflection(reflection)
|
|
203
|
-
if reflection
|
|
204
|
-
if reflection.options[:polymorphic]
|
|
205
|
-
read_attribute(reflection.foreign_type)&.constantize&.connection_classes
|
|
206
|
-
else
|
|
207
|
-
reflection.klass.connection_classes
|
|
208
|
-
end
|
|
209
|
-
else
|
|
210
|
-
self.class.connection_classes
|
|
211
|
-
end
|
|
212
|
-
end
|
|
213
304
|
end
|
|
214
305
|
end
|
|
215
306
|
end
|