switchman 3.0.9 → 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 +11 -4
- 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 +20 -11
- 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/database_server.rb +26 -18
- 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 +3 -0
- data/lib/switchman/rails.rb +2 -5
- data/{app/models → lib}/switchman/shard.rb +28 -133
- 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 +20 -20
- data/lib/switchman/active_record/association.rb +0 -206
- data/lib/switchman/open4.rb +0 -80
|
@@ -14,8 +14,6 @@ module Switchman
|
|
|
14
14
|
def sharded_model
|
|
15
15
|
self.abstract_class = true
|
|
16
16
|
|
|
17
|
-
return if self == UnshardedRecord
|
|
18
|
-
|
|
19
17
|
Shard.send(:add_sharded_model, self)
|
|
20
18
|
end
|
|
21
19
|
|
|
@@ -27,20 +25,12 @@ module Switchman
|
|
|
27
25
|
def transaction(**)
|
|
28
26
|
if self != ::ActiveRecord::Base && current_scope
|
|
29
27
|
current_scope.activate do
|
|
30
|
-
db = Shard.current(
|
|
31
|
-
if ::GuardRail.environment == db.guard_rail_environment
|
|
32
|
-
super
|
|
33
|
-
else
|
|
34
|
-
db.unguard { super }
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
else
|
|
38
|
-
db = Shard.current(connection_classes).database_server
|
|
39
|
-
if ::GuardRail.environment == db.guard_rail_environment
|
|
40
|
-
super
|
|
41
|
-
else
|
|
28
|
+
db = Shard.current(connection_class_for_self).database_server
|
|
42
29
|
db.unguard { super }
|
|
43
30
|
end
|
|
31
|
+
else
|
|
32
|
+
db = Shard.current(connection_class_for_self).database_server
|
|
33
|
+
db.unguard { super }
|
|
44
34
|
end
|
|
45
35
|
end
|
|
46
36
|
|
|
@@ -68,11 +58,56 @@ module Switchman
|
|
|
68
58
|
end
|
|
69
59
|
end
|
|
70
60
|
|
|
61
|
+
def role_overriden?(shard_id)
|
|
62
|
+
current_role(target_shard: shard_id) != current_role(without_overrides: true)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def establish_connection(config_or_env = nil)
|
|
66
|
+
raise ArgumentError, 'establish connection cannot be used on the non-current shard/role' if config_or_env.is_a?(Symbol) && config_or_env != ::Rails.env.to_sym
|
|
67
|
+
|
|
68
|
+
# Ensure we don't randomly surprise change the connection parms associated with a shard/role
|
|
69
|
+
config_or_env = nil if config_or_env == ::Rails.env.to_sym
|
|
70
|
+
|
|
71
|
+
config_or_env ||= if current_shard == ::Rails.env.to_sym && current_role == :primary
|
|
72
|
+
:primary
|
|
73
|
+
else
|
|
74
|
+
"#{current_shard}/#{current_role}".to_sym
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
super(config_or_env)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def connected_to_stack
|
|
81
|
+
return super if ::Rails.version < '7.0' ? Thread.current.thread_variable?(:ar_connected_to_stack) : ::ActiveSupport::IsolatedExecutionState.key?(:active_record_connected_to_stack)
|
|
82
|
+
|
|
83
|
+
ret = super
|
|
84
|
+
DatabaseServer.guard_servers
|
|
85
|
+
ret
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# significant change: Allow per-shard roles
|
|
89
|
+
def current_role(without_overrides: false, target_shard: current_shard)
|
|
90
|
+
return super() if without_overrides
|
|
91
|
+
|
|
92
|
+
sharded_role = nil
|
|
93
|
+
connected_to_stack.reverse_each do |hash|
|
|
94
|
+
shard_role = hash.dig(:shard_roles, target_shard)
|
|
95
|
+
if shard_role && (hash[:klasses].include?(::ActiveRecord::Base) || hash[:klasses].include?(connection_class_for_self))
|
|
96
|
+
sharded_role = shard_role
|
|
97
|
+
break
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
# Allow a shard-specific role to be reverted to regular inheritance
|
|
101
|
+
return sharded_role if sharded_role && sharded_role != :_switchman_inherit
|
|
102
|
+
|
|
103
|
+
super()
|
|
104
|
+
end
|
|
105
|
+
|
|
71
106
|
# significant change: _don't_ check if klasses.include?(Base)
|
|
72
107
|
# i.e. other sharded models don't inherit the current shard of Base
|
|
73
108
|
def current_shard
|
|
74
109
|
connected_to_stack.reverse_each do |hash|
|
|
75
|
-
return hash[:shard] if hash[:shard] && hash[:klasses].include?(
|
|
110
|
+
return hash[:shard] if hash[:shard] && hash[:klasses].include?(connection_class_for_self)
|
|
76
111
|
end
|
|
77
112
|
|
|
78
113
|
default_shard
|
|
@@ -80,11 +115,17 @@ module Switchman
|
|
|
80
115
|
|
|
81
116
|
def current_switchman_shard
|
|
82
117
|
connected_to_stack.reverse_each do |hash|
|
|
83
|
-
return hash[:switchman_shard] if hash[:switchman_shard] && hash[:klasses].include?(
|
|
118
|
+
return hash[:switchman_shard] if hash[:switchman_shard] && hash[:klasses].include?(connection_class_for_self)
|
|
84
119
|
end
|
|
85
120
|
|
|
86
121
|
Shard.default
|
|
87
122
|
end
|
|
123
|
+
|
|
124
|
+
if ::Rails.version < '7.0'
|
|
125
|
+
def connection_class_for_self
|
|
126
|
+
connection_classes
|
|
127
|
+
end
|
|
128
|
+
end
|
|
88
129
|
end
|
|
89
130
|
|
|
90
131
|
def self.prepended(klass)
|
|
@@ -93,15 +134,15 @@ module Switchman
|
|
|
93
134
|
|
|
94
135
|
def _run_initialize_callbacks
|
|
95
136
|
@shard ||= if self.class.sharded_primary_key?
|
|
96
|
-
Shard.shard_for(self[self.class.primary_key], Shard.current(self.class.
|
|
137
|
+
Shard.shard_for(self[self.class.primary_key], Shard.current(self.class.connection_class_for_self))
|
|
97
138
|
else
|
|
98
|
-
Shard.current(self.class.
|
|
139
|
+
Shard.current(self.class.connection_class_for_self)
|
|
99
140
|
end
|
|
100
141
|
super
|
|
101
142
|
end
|
|
102
143
|
|
|
103
144
|
def shard
|
|
104
|
-
@shard || Shard.current(self.class.
|
|
145
|
+
@shard || Shard.current(self.class.connection_class_for_self) || Shard.default
|
|
105
146
|
end
|
|
106
147
|
|
|
107
148
|
def shard=(new_shard)
|
|
@@ -126,7 +167,7 @@ module Switchman
|
|
|
126
167
|
end
|
|
127
168
|
|
|
128
169
|
def destroy
|
|
129
|
-
shard.activate(self.class.
|
|
170
|
+
shard.activate(self.class.connection_class_for_self) { super }
|
|
130
171
|
end
|
|
131
172
|
|
|
132
173
|
def clone
|
|
@@ -139,14 +180,15 @@ module Switchman
|
|
|
139
180
|
end
|
|
140
181
|
|
|
141
182
|
def transaction(**kwargs, &block)
|
|
142
|
-
shard.activate(self.class.
|
|
183
|
+
shard.activate(self.class.connection_class_for_self) do
|
|
143
184
|
self.class.transaction(**kwargs, &block)
|
|
144
185
|
end
|
|
145
186
|
end
|
|
146
187
|
|
|
147
188
|
def with_transaction_returning_status
|
|
148
|
-
shard.activate(self.class.
|
|
149
|
-
|
|
189
|
+
shard.activate(self.class.connection_class_for_self) do
|
|
190
|
+
db = Shard.current(self.class.connection_class_for_self).database_server
|
|
191
|
+
db.unguard { super }
|
|
150
192
|
end
|
|
151
193
|
end
|
|
152
194
|
|
|
@@ -167,9 +209,7 @@ module Switchman
|
|
|
167
209
|
|
|
168
210
|
def update_columns(*)
|
|
169
211
|
db = shard.database_server
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
super
|
|
212
|
+
db.unguard { super }
|
|
173
213
|
end
|
|
174
214
|
|
|
175
215
|
def id_for_database
|
|
@@ -184,22 +224,22 @@ module Switchman
|
|
|
184
224
|
|
|
185
225
|
protected
|
|
186
226
|
|
|
187
|
-
# see also AttributeMethods#
|
|
188
|
-
def
|
|
227
|
+
# see also AttributeMethods#connection_class_for_self_code_for_reflection
|
|
228
|
+
def connection_class_for_self_for_reflection(reflection)
|
|
189
229
|
if reflection
|
|
190
230
|
if reflection.options[:polymorphic]
|
|
191
231
|
begin
|
|
192
|
-
read_attribute(reflection.foreign_type)&.constantize&.
|
|
232
|
+
read_attribute(reflection.foreign_type)&.constantize&.connection_class_for_self || ::ActiveRecord::Base
|
|
193
233
|
rescue NameError
|
|
194
234
|
# in case someone is abusing foreign_type to not point to an actual class
|
|
195
235
|
::ActiveRecord::Base
|
|
196
236
|
end
|
|
197
237
|
else
|
|
198
238
|
# otherwise we can just return a symbol for the statically known type of the association
|
|
199
|
-
reflection.klass.
|
|
239
|
+
reflection.klass.connection_class_for_self
|
|
200
240
|
end
|
|
201
241
|
else
|
|
202
|
-
self.class.
|
|
242
|
+
self.class.connection_class_for_self
|
|
203
243
|
end
|
|
204
244
|
end
|
|
205
245
|
end
|
|
@@ -4,7 +4,7 @@ module Switchman
|
|
|
4
4
|
module ActiveRecord
|
|
5
5
|
module Calculations
|
|
6
6
|
def pluck(*column_names)
|
|
7
|
-
target_shard = Shard.current(klass.
|
|
7
|
+
target_shard = Shard.current(klass.connection_class_for_self)
|
|
8
8
|
shard_count = 0
|
|
9
9
|
result = activate do |relation, shard|
|
|
10
10
|
shard_count += 1
|
|
@@ -109,11 +109,18 @@ module Switchman
|
|
|
109
109
|
private
|
|
110
110
|
|
|
111
111
|
def type_cast_calculated_value_switchman(value, column_name, operation)
|
|
112
|
-
|
|
112
|
+
if ::Rails.version < '7.0'
|
|
113
|
+
type_cast_calculated_value(value, operation) do |val|
|
|
114
|
+
column = aggregate_column(column_name)
|
|
115
|
+
type ||= column.try(:type_caster) ||
|
|
116
|
+
lookup_cast_type_from_join_dependencies(column_name.to_s) || ::ActiveRecord::Type.default_value
|
|
117
|
+
type.deserialize(val)
|
|
118
|
+
end
|
|
119
|
+
else
|
|
113
120
|
column = aggregate_column(column_name)
|
|
114
121
|
type ||= column.try(:type_caster) ||
|
|
115
|
-
lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
|
|
116
|
-
type
|
|
122
|
+
lookup_cast_type_from_join_dependencies(column_name.to_s) || ::ActiveRecord::Type.default_value
|
|
123
|
+
type_cast_calculated_value(value, operation, type)
|
|
117
124
|
end
|
|
118
125
|
end
|
|
119
126
|
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'switchman/errors'
|
|
4
|
-
|
|
5
3
|
module Switchman
|
|
6
4
|
module ActiveRecord
|
|
7
5
|
module ConnectionPool
|
|
@@ -20,7 +18,7 @@ module Switchman
|
|
|
20
18
|
|
|
21
19
|
def connection(switch_shard: true)
|
|
22
20
|
conn = super()
|
|
23
|
-
raise NonExistentShardError if current_shard.new_record?
|
|
21
|
+
raise Errors::NonExistentShardError if current_shard.new_record?
|
|
24
22
|
|
|
25
23
|
switch_database(conn) if conn.shard != current_shard && switch_shard
|
|
26
24
|
conn
|
|
@@ -41,7 +39,7 @@ module Switchman
|
|
|
41
39
|
private
|
|
42
40
|
|
|
43
41
|
def current_shard
|
|
44
|
-
connection_klass.current_switchman_shard
|
|
42
|
+
::Rails.version < '7.0' ? connection_klass.current_switchman_shard : connection_class.current_switchman_shard
|
|
45
43
|
end
|
|
46
44
|
|
|
47
45
|
def tls_key
|
|
@@ -3,6 +3,20 @@
|
|
|
3
3
|
module Switchman
|
|
4
4
|
module ActiveRecord
|
|
5
5
|
module DatabaseConfigurations
|
|
6
|
+
# key difference: For each env name, ensure only one writable config is returned
|
|
7
|
+
# since all should point to the same data, even if multiple are writable
|
|
8
|
+
# (Picks 'primary' since it is guaranteed to exist and switchman handles activating
|
|
9
|
+
# deploy through other means)
|
|
10
|
+
def configs_for(include_replicas: false, name: nil, **)
|
|
11
|
+
res = super
|
|
12
|
+
if name && !include_replicas
|
|
13
|
+
return nil unless name.end_with?('primary')
|
|
14
|
+
elsif !include_replicas
|
|
15
|
+
return res.select { |config| config.name.end_with?('primary') }
|
|
16
|
+
end
|
|
17
|
+
res
|
|
18
|
+
end
|
|
19
|
+
|
|
6
20
|
private
|
|
7
21
|
|
|
8
22
|
# key difference: assumes a hybrid two-tier structure; each third tier
|
|
@@ -13,7 +27,9 @@ module Switchman
|
|
|
13
27
|
return configs if configs.is_a?(Array)
|
|
14
28
|
|
|
15
29
|
db_configs = configs.flat_map do |env_name, config|
|
|
16
|
-
|
|
30
|
+
# It would be nice to do the auto-fallback that we want here, but we haven't
|
|
31
|
+
# actually done that for years (or maybe ever) and it will be a big lift to get working
|
|
32
|
+
roles = config.keys.select { |k| config[k].is_a?(Hash) || (config[k].is_a?(Array) && config[k].all? { |ck| ck.is_a?(Hash) }) }
|
|
17
33
|
base_config = config.except(*roles)
|
|
18
34
|
|
|
19
35
|
name = "#{env_name}/primary"
|
|
@@ -21,7 +37,7 @@ module Switchman
|
|
|
21
37
|
base_db = build_db_config_from_raw_config(env_name, name, base_config)
|
|
22
38
|
[base_db] + roles.map do |role|
|
|
23
39
|
build_db_config_from_raw_config(env_name, "#{env_name}/#{role}",
|
|
24
|
-
base_config.merge(config[role]).
|
|
40
|
+
base_config.merge(config[role].is_a?(Array) ? config[role].first : config[role]))
|
|
25
41
|
end
|
|
26
42
|
end
|
|
27
43
|
|
|
@@ -7,7 +7,7 @@ module Switchman
|
|
|
7
7
|
return super(id) unless klass.integral_id?
|
|
8
8
|
|
|
9
9
|
if shard_source_value != :implicit
|
|
10
|
-
current_shard = Shard.current(klass.
|
|
10
|
+
current_shard = Shard.current(klass.connection_class_for_self)
|
|
11
11
|
result = activate do |relation, shard|
|
|
12
12
|
current_id = Shard.relative_id_for(id, current_shard, shard)
|
|
13
13
|
# current_id will be nil for non-integral id
|
|
@@ -33,7 +33,7 @@ module Switchman
|
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
def find_some_ordered(ids)
|
|
36
|
-
current_shard = Shard.current(klass.
|
|
36
|
+
current_shard = Shard.current(klass.connection_class_for_self)
|
|
37
37
|
ids = ids.map { |id| Shard.relative_id_for(id, current_shard, current_shard) }
|
|
38
38
|
super(ids)
|
|
39
39
|
end
|
|
@@ -6,7 +6,7 @@ module Switchman
|
|
|
6
6
|
module ClassMethods
|
|
7
7
|
def quoted_table_name
|
|
8
8
|
@quoted_table_name ||= {}
|
|
9
|
-
@quoted_table_name[Shard.current(
|
|
9
|
+
@quoted_table_name[Shard.current(connection_class_for_self).id] ||= connection.quote_table_name(table_name)
|
|
10
10
|
end
|
|
11
11
|
end
|
|
12
12
|
end
|
|
@@ -5,18 +5,16 @@ module Switchman
|
|
|
5
5
|
module Persistence
|
|
6
6
|
# touch reads the id attribute directly, so it's not relative to the current shard
|
|
7
7
|
def touch(*, **)
|
|
8
|
-
shard.activate(self.class.
|
|
8
|
+
shard.activate(self.class.connection_class_for_self) { super }
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def update_columns(*)
|
|
12
|
-
shard.activate(self.class.
|
|
12
|
+
shard.activate(self.class.connection_class_for_self) { super }
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def delete
|
|
16
16
|
db = shard.database_server
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
super
|
|
17
|
+
db.unguard { super }
|
|
20
18
|
end
|
|
21
19
|
end
|
|
22
20
|
end
|
|
@@ -7,7 +7,7 @@ module Switchman
|
|
|
7
7
|
def create_database(name, options = {})
|
|
8
8
|
options = { encoding: 'utf8' }.merge!(options.symbolize_keys)
|
|
9
9
|
|
|
10
|
-
option_string = options.sum do |key, value|
|
|
10
|
+
option_string = options.sum('') do |key, value|
|
|
11
11
|
case key
|
|
12
12
|
when :owner
|
|
13
13
|
" OWNER = \"#{value}\""
|
|
@@ -65,7 +65,7 @@ module Switchman
|
|
|
65
65
|
when ::ActiveRecord::Relation
|
|
66
66
|
Shard.default
|
|
67
67
|
when nil
|
|
68
|
-
Shard.current(klass.
|
|
68
|
+
Shard.current(klass.connection_class_for_self)
|
|
69
69
|
else
|
|
70
70
|
raise ArgumentError, "invalid shard value #{shard_value}"
|
|
71
71
|
end
|
|
@@ -79,7 +79,7 @@ module Switchman
|
|
|
79
79
|
when ::ActiveRecord::Base
|
|
80
80
|
shard_value.respond_to?(:associated_shards) ? shard_value.associated_shards : [shard_value.shard]
|
|
81
81
|
when nil
|
|
82
|
-
[Shard.current(klass.
|
|
82
|
+
[Shard.current(klass.connection_class_for_self)]
|
|
83
83
|
else
|
|
84
84
|
shard_value
|
|
85
85
|
end
|
|
@@ -131,7 +131,7 @@ module Switchman
|
|
|
131
131
|
id_shards = Set.new
|
|
132
132
|
right.each do |value|
|
|
133
133
|
local_id, id_shard = Shard.local_id_for(value)
|
|
134
|
-
id_shard ||= Shard.current(klass.
|
|
134
|
+
id_shard ||= Shard.current(klass.connection_class_for_self) if local_id
|
|
135
135
|
id_shards << id_shard if id_shard
|
|
136
136
|
end
|
|
137
137
|
return if id_shards.empty?
|
|
@@ -151,10 +151,13 @@ module Switchman
|
|
|
151
151
|
end
|
|
152
152
|
when ::Arel::Nodes::BindParam
|
|
153
153
|
local_id, id_shard = Shard.local_id_for(right.value.value_before_type_cast)
|
|
154
|
-
id_shard ||= Shard.current(klass.
|
|
154
|
+
id_shard ||= Shard.current(klass.connection_class_for_self) if local_id
|
|
155
|
+
when ::ActiveModel::Attribute
|
|
156
|
+
local_id, id_shard = Shard.local_id_for(right.value_before_type_cast)
|
|
157
|
+
id_shard ||= Shard.current(klass.connection_class_for_self) if local_id
|
|
155
158
|
else
|
|
156
159
|
local_id, id_shard = Shard.local_id_for(right)
|
|
157
|
-
id_shard ||= Shard.current(klass.
|
|
160
|
+
id_shard ||= Shard.current(klass.connection_class_for_self) if local_id
|
|
158
161
|
end
|
|
159
162
|
|
|
160
163
|
return if !id_shard || id_shard == primary_shard
|
|
@@ -193,9 +196,9 @@ module Switchman
|
|
|
193
196
|
reflection = model.send(:reflection_for_integer_attribute, column)
|
|
194
197
|
break if reflection
|
|
195
198
|
end
|
|
196
|
-
return Shard.current(klass.
|
|
199
|
+
return Shard.current(klass.connection_class_for_self) if reflection.options[:polymorphic]
|
|
197
200
|
|
|
198
|
-
Shard.current(reflection.klass.
|
|
201
|
+
Shard.current(reflection.klass.connection_class_for_self)
|
|
199
202
|
end
|
|
200
203
|
|
|
201
204
|
def relation_and_column(attribute)
|
|
@@ -291,7 +294,7 @@ module Switchman
|
|
|
291
294
|
if source_shard
|
|
292
295
|
source_shard
|
|
293
296
|
elsif type == :primary
|
|
294
|
-
Shard.current(klass.
|
|
297
|
+
Shard.current(klass.connection_class_for_self)
|
|
295
298
|
elsif type == :foreign
|
|
296
299
|
source_shard_for_foreign_key(relation, column)
|
|
297
300
|
end
|
|
@@ -332,8 +335,9 @@ module Switchman
|
|
|
332
335
|
end
|
|
333
336
|
|
|
334
337
|
def transpose_predicate_value(value, current_shard, target_shard, attribute_type, remove_non_local_ids)
|
|
335
|
-
|
|
336
|
-
|
|
338
|
+
case value
|
|
339
|
+
when ::Arel::Nodes::BindParam, ::ActiveModel::Attribute
|
|
340
|
+
query_att = value.is_a?(::ActiveModel::Attribute) ? value : value.value
|
|
337
341
|
current_id = query_att.value_before_type_cast
|
|
338
342
|
if current_id.is_a?(::ActiveRecord::StatementCache::Substitute)
|
|
339
343
|
current_id.sharded = true # mark for transposition later
|
|
@@ -346,7 +350,12 @@ module Switchman
|
|
|
346
350
|
# make a new bind param
|
|
347
351
|
value
|
|
348
352
|
else
|
|
349
|
-
|
|
353
|
+
new_att = query_att.class.new(query_att.name, local_id, query_att.type)
|
|
354
|
+
if value.is_a?(::ActiveModel::Attribute)
|
|
355
|
+
new_att
|
|
356
|
+
else
|
|
357
|
+
::Arel::Nodes::BindParam.new(new_att)
|
|
358
|
+
end
|
|
350
359
|
end
|
|
351
360
|
end
|
|
352
361
|
else
|
|
@@ -5,7 +5,7 @@ module Switchman
|
|
|
5
5
|
module Reflection
|
|
6
6
|
module AbstractReflection
|
|
7
7
|
def shard(owner)
|
|
8
|
-
if polymorphic? || klass.
|
|
8
|
+
if polymorphic? || klass.connection_class_for_self == owner.class.connection_class_for_self
|
|
9
9
|
# polymorphic associations assume the same shard as the owning item
|
|
10
10
|
owner.shard
|
|
11
11
|
else
|
|
@@ -9,13 +9,13 @@ module Switchman
|
|
|
9
9
|
|
|
10
10
|
def initialize(*, **)
|
|
11
11
|
super
|
|
12
|
-
self.shard_value = Shard.current(klass ? klass.
|
|
12
|
+
self.shard_value = Shard.current(klass ? klass.connection_class_for_self : :primary) unless shard_value
|
|
13
13
|
self.shard_source_value = :implicit unless shard_source_value
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def clone
|
|
17
17
|
result = super
|
|
18
|
-
result.shard_value = Shard.current(klass ? klass.
|
|
18
|
+
result.shard_value = Shard.current(klass ? klass.connection_class_for_self : :primary) unless shard_value
|
|
19
19
|
result
|
|
20
20
|
end
|
|
21
21
|
|
|
@@ -29,35 +29,32 @@ module Switchman
|
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
def new(*, &block)
|
|
32
|
-
primary_shard.activate(klass.
|
|
32
|
+
primary_shard.activate(klass.connection_class_for_self) { super }
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
def create(*, &block)
|
|
36
|
-
primary_shard.activate(klass.
|
|
36
|
+
primary_shard.activate(klass.connection_class_for_self) { super }
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
def create!(*, &block)
|
|
40
|
-
primary_shard.activate(klass.
|
|
40
|
+
primary_shard.activate(klass.connection_class_for_self) { super }
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
def to_sql
|
|
44
|
-
primary_shard.activate(klass.
|
|
44
|
+
primary_shard.activate(klass.connection_class_for_self) { super }
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
def explain
|
|
48
48
|
activate { |relation| relation.call_super(:explain, Relation) }
|
|
49
49
|
end
|
|
50
50
|
|
|
51
|
-
def
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
results = activate { |relation| relation.call_super(:records, Relation) }
|
|
55
|
-
case shard_value
|
|
56
|
-
when Array, ::ActiveRecord::Relation, ::ActiveRecord::Base
|
|
57
|
-
@records = results
|
|
51
|
+
def load(&block)
|
|
52
|
+
if !loaded? || (::Rails.version >= '7.0' && scheduled?)
|
|
53
|
+
@records = activate { |relation| relation.send(:exec_queries, &block) }
|
|
58
54
|
@loaded = true
|
|
59
55
|
end
|
|
60
|
-
|
|
56
|
+
|
|
57
|
+
self
|
|
61
58
|
end
|
|
62
59
|
|
|
63
60
|
%I[update_all delete_all].each do |method|
|
|
@@ -106,19 +103,19 @@ module Switchman
|
|
|
106
103
|
def activate(unordered: false, &block)
|
|
107
104
|
shards = all_shards
|
|
108
105
|
if Array === shards && shards.length == 1
|
|
109
|
-
if shards.first == DefaultShard || shards.first == Shard.current(klass.
|
|
106
|
+
if shards.first == DefaultShard || shards.first == Shard.current(klass.connection_class_for_self)
|
|
110
107
|
yield(self, shards.first)
|
|
111
108
|
else
|
|
112
|
-
shards.first.activate(klass.
|
|
109
|
+
shards.first.activate(klass.connection_class_for_self) { yield(self, shards.first) }
|
|
113
110
|
end
|
|
114
111
|
else
|
|
115
112
|
result_count = 0
|
|
116
113
|
can_order = false
|
|
117
|
-
result = Shard.with_each_shard(shards, [klass.
|
|
114
|
+
result = Shard.with_each_shard(shards, [klass.connection_class_for_self]) do
|
|
118
115
|
# don't even query other shards if we're already past the limit
|
|
119
116
|
next if limit_value && result_count >= limit_value && order_values.empty?
|
|
120
117
|
|
|
121
|
-
relation = shard(Shard.current(klass.
|
|
118
|
+
relation = shard(Shard.current(klass.connection_class_for_self), :to_a)
|
|
122
119
|
# do a minimal query if possible
|
|
123
120
|
relation = relation.limit(limit_value - result_count) if limit_value && !result_count.zero? && order_values.empty?
|
|
124
121
|
|
|
@@ -33,12 +33,12 @@ module Switchman
|
|
|
33
33
|
primary_value = params[primary_index]
|
|
34
34
|
target_shard = Shard.local_id_for(primary_value)[1]
|
|
35
35
|
end
|
|
36
|
-
current_shard = Shard.current(klass.
|
|
36
|
+
current_shard = Shard.current(klass.connection_class_for_self)
|
|
37
37
|
target_shard ||= current_shard
|
|
38
38
|
|
|
39
39
|
bind_values = bind_map.bind(params, current_shard, target_shard)
|
|
40
40
|
|
|
41
|
-
target_shard.activate(klass.
|
|
41
|
+
target_shard.activate(klass.connection_class_for_self) do
|
|
42
42
|
sql = qualified_query_builder(target_shard, klass).sql_for(bind_values, connection)
|
|
43
43
|
klass.find_by_sql(sql, bind_values)
|
|
44
44
|
end
|
|
@@ -4,6 +4,22 @@ module Switchman
|
|
|
4
4
|
module ActiveSupport
|
|
5
5
|
module Cache
|
|
6
6
|
module ClassMethods
|
|
7
|
+
def lookup_stores(cache_store_config)
|
|
8
|
+
result = {}
|
|
9
|
+
cache_store_config.each do |key, value|
|
|
10
|
+
next if value.is_a?(String)
|
|
11
|
+
|
|
12
|
+
result[key] = ::ActiveSupport::Cache.lookup_store(value)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
cache_store_config.each do |key, value| # rubocop:disable Style/CombinableLoops
|
|
16
|
+
next unless value.is_a?(String)
|
|
17
|
+
|
|
18
|
+
result[key] = result[value]
|
|
19
|
+
end
|
|
20
|
+
result
|
|
21
|
+
end
|
|
22
|
+
|
|
7
23
|
def lookup_store(*store_options)
|
|
8
24
|
store = super
|
|
9
25
|
# can't use defined?, because it's a _ruby_ autoloaded constant,
|