switchman 3.0.2 → 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/db/migrate/20180828183945_add_default_shard_index.rb +1 -1
- data/db/migrate/20180828192111_add_timestamps_to_shards.rb +1 -1
- data/lib/switchman/action_controller/caching.rb +2 -2
- data/lib/switchman/active_record/abstract_adapter.rb +2 -13
- data/lib/switchman/active_record/associations.rb +223 -0
- data/lib/switchman/active_record/attribute_methods.rb +144 -63
- data/lib/switchman/active_record/base.rb +100 -43
- data/lib/switchman/active_record/calculations.rb +12 -5
- data/lib/switchman/active_record/connection_pool.rb +9 -31
- 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/migration.rb +7 -4
- data/lib/switchman/active_record/model_schema.rb +1 -1
- data/lib/switchman/active_record/persistence.rb +7 -2
- data/lib/switchman/active_record/postgresql_adapter.rb +6 -2
- data/lib/switchman/active_record/predicate_builder.rb +1 -1
- data/lib/switchman/active_record/query_methods.rb +27 -14
- data/lib/switchman/active_record/reflection.rb +1 -1
- data/lib/switchman/active_record/relation.rb +25 -24
- 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/test_fixtures.rb +43 -0
- data/lib/switchman/active_support/cache.rb +16 -0
- data/lib/switchman/arel.rb +28 -6
- data/lib/switchman/database_server.rb +71 -65
- data/lib/switchman/default_shard.rb +0 -2
- data/lib/switchman/engine.rb +67 -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 +5 -17
- data/lib/switchman/rails.rb +1 -4
- data/{app/models → lib}/switchman/shard.rb +61 -188
- data/lib/switchman/sharded_instrumenter.rb +1 -1
- data/lib/switchman/standard_error.rb +11 -12
- 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 +24 -13
- metadata +24 -22
- 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,30 +58,91 @@ 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
|
|
79
114
|
end
|
|
115
|
+
|
|
116
|
+
def current_switchman_shard
|
|
117
|
+
connected_to_stack.reverse_each do |hash|
|
|
118
|
+
return hash[:switchman_shard] if hash[:switchman_shard] && hash[:klasses].include?(connection_class_for_self)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
Shard.default
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
if ::Rails.version < '7.0'
|
|
125
|
+
def connection_class_for_self
|
|
126
|
+
connection_classes
|
|
127
|
+
end
|
|
128
|
+
end
|
|
80
129
|
end
|
|
81
130
|
|
|
82
|
-
def self.
|
|
131
|
+
def self.prepended(klass)
|
|
83
132
|
klass.singleton_class.prepend(ClassMethods)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def _run_initialize_callbacks
|
|
136
|
+
@shard ||= if self.class.sharded_primary_key?
|
|
137
|
+
Shard.shard_for(self[self.class.primary_key], Shard.current(self.class.connection_class_for_self))
|
|
138
|
+
else
|
|
139
|
+
Shard.current(self.class.connection_class_for_self)
|
|
140
|
+
end
|
|
141
|
+
super
|
|
91
142
|
end
|
|
92
143
|
|
|
93
144
|
def shard
|
|
94
|
-
@shard || Shard.current(self.class.
|
|
145
|
+
@shard || Shard.current(self.class.connection_class_for_self) || Shard.default
|
|
95
146
|
end
|
|
96
147
|
|
|
97
148
|
def shard=(new_shard)
|
|
@@ -107,16 +158,16 @@ module Switchman
|
|
|
107
158
|
|
|
108
159
|
def save(*, **)
|
|
109
160
|
@shard_set_in_stone = true
|
|
110
|
-
|
|
161
|
+
super
|
|
111
162
|
end
|
|
112
163
|
|
|
113
164
|
def save!(*, **)
|
|
114
165
|
@shard_set_in_stone = true
|
|
115
|
-
|
|
166
|
+
super
|
|
116
167
|
end
|
|
117
168
|
|
|
118
169
|
def destroy
|
|
119
|
-
shard.activate(self.class.
|
|
170
|
+
shard.activate(self.class.connection_class_for_self) { super }
|
|
120
171
|
end
|
|
121
172
|
|
|
122
173
|
def clone
|
|
@@ -129,11 +180,18 @@ module Switchman
|
|
|
129
180
|
end
|
|
130
181
|
|
|
131
182
|
def transaction(**kwargs, &block)
|
|
132
|
-
shard.activate(self.class.
|
|
183
|
+
shard.activate(self.class.connection_class_for_self) do
|
|
133
184
|
self.class.transaction(**kwargs, &block)
|
|
134
185
|
end
|
|
135
186
|
end
|
|
136
187
|
|
|
188
|
+
def with_transaction_returning_status
|
|
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 }
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
137
195
|
def hash
|
|
138
196
|
self.class.sharded_primary_key? ? self.class.hash ^ global_id.hash : super
|
|
139
197
|
end
|
|
@@ -149,40 +207,39 @@ module Switchman
|
|
|
149
207
|
copy
|
|
150
208
|
end
|
|
151
209
|
|
|
152
|
-
def
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
# do this the Rails 4.2 way, so that if Shard.current != self.shard, the id gets transposed
|
|
156
|
-
self.class.connection.quote(id)
|
|
210
|
+
def update_columns(*)
|
|
211
|
+
db = shard.database_server
|
|
212
|
+
db.unguard { super }
|
|
157
213
|
end
|
|
158
214
|
|
|
159
|
-
def
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
215
|
+
def id_for_database
|
|
216
|
+
if self.class.sharded_primary_key?
|
|
217
|
+
# It's an int, so so it's safe to just return it without passing it through anything else
|
|
218
|
+
# In theory we should do `@attributes[@primary_key].type.serialize(id)`, but that seems to have surprising side-effects
|
|
219
|
+
id
|
|
163
220
|
else
|
|
164
|
-
|
|
221
|
+
super
|
|
165
222
|
end
|
|
166
223
|
end
|
|
167
224
|
|
|
168
225
|
protected
|
|
169
226
|
|
|
170
|
-
# see also AttributeMethods#
|
|
171
|
-
def
|
|
227
|
+
# see also AttributeMethods#connection_class_for_self_code_for_reflection
|
|
228
|
+
def connection_class_for_self_for_reflection(reflection)
|
|
172
229
|
if reflection
|
|
173
230
|
if reflection.options[:polymorphic]
|
|
174
231
|
begin
|
|
175
|
-
read_attribute(reflection.foreign_type)&.constantize&.
|
|
232
|
+
read_attribute(reflection.foreign_type)&.constantize&.connection_class_for_self || ::ActiveRecord::Base
|
|
176
233
|
rescue NameError
|
|
177
234
|
# in case someone is abusing foreign_type to not point to an actual class
|
|
178
235
|
::ActiveRecord::Base
|
|
179
236
|
end
|
|
180
237
|
else
|
|
181
238
|
# otherwise we can just return a symbol for the statically known type of the association
|
|
182
|
-
reflection.klass.
|
|
239
|
+
reflection.klass.connection_class_for_self
|
|
183
240
|
end
|
|
184
241
|
else
|
|
185
|
-
|
|
242
|
+
self.class.connection_class_for_self
|
|
186
243
|
end
|
|
187
244
|
end
|
|
188
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
|
|
@@ -83,7 +83,7 @@ module Switchman
|
|
|
83
83
|
if opts[:association]
|
|
84
84
|
key_ids = calculated_data.collect { |row| row[opts[:group_aliases].first] }
|
|
85
85
|
key_records = opts[:association].klass.base_class.where(id: key_ids)
|
|
86
|
-
key_records = key_records.
|
|
86
|
+
key_records = key_records.to_h { |r| [Shard.relative_id_for(r, shard, target_shard), r] }
|
|
87
87
|
end
|
|
88
88
|
|
|
89
89
|
calculated_data.map do |row|
|
|
@@ -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,22 +1,8 @@
|
|
|
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
|
|
8
|
-
def shard
|
|
9
|
-
shard_stack.last || Shard.default
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def shard_stack
|
|
13
|
-
unless (shard_stack = Thread.current.thread_variable_get(tls_key))
|
|
14
|
-
shard_stack = Concurrent::Array.new
|
|
15
|
-
Thread.current.thread_variable_set(tls_key, shard_stack)
|
|
16
|
-
end
|
|
17
|
-
shard_stack
|
|
18
|
-
end
|
|
19
|
-
|
|
20
6
|
def default_schema
|
|
21
7
|
connection unless @schemas
|
|
22
8
|
# default shard will not switch databases immediately, so it won't be set yet
|
|
@@ -26,15 +12,15 @@ module Switchman
|
|
|
26
12
|
|
|
27
13
|
def checkout_new_connection
|
|
28
14
|
conn = super
|
|
29
|
-
conn.shard =
|
|
15
|
+
conn.shard = current_shard
|
|
30
16
|
conn
|
|
31
17
|
end
|
|
32
18
|
|
|
33
19
|
def connection(switch_shard: true)
|
|
34
20
|
conn = super()
|
|
35
|
-
raise NonExistentShardError if
|
|
21
|
+
raise Errors::NonExistentShardError if current_shard.new_record?
|
|
36
22
|
|
|
37
|
-
switch_database(conn) if conn.shard !=
|
|
23
|
+
switch_database(conn) if conn.shard != current_shard && switch_shard
|
|
38
24
|
conn
|
|
39
25
|
end
|
|
40
26
|
|
|
@@ -44,26 +30,18 @@ module Switchman
|
|
|
44
30
|
flush
|
|
45
31
|
end
|
|
46
32
|
|
|
47
|
-
def remove_shard!(shard)
|
|
48
|
-
synchronize do
|
|
49
|
-
# The shard might be currently active, so we need to update our own shard
|
|
50
|
-
self.shard = Shard.default if self.shard == shard
|
|
51
|
-
# Update out any connections that may be using this shard
|
|
52
|
-
@connections.each do |conn|
|
|
53
|
-
# This will also update the connection's shard to the default shard
|
|
54
|
-
switch_database(conn) if conn.shard == shard
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
|
|
59
33
|
def switch_database(conn)
|
|
60
|
-
@schemas = conn.current_schemas if !@schemas && conn.adapter_name == 'PostgreSQL' && !
|
|
34
|
+
@schemas = conn.current_schemas if !@schemas && conn.adapter_name == 'PostgreSQL' && !current_shard.database_server.config[:shard_name]
|
|
61
35
|
|
|
62
|
-
conn.shard =
|
|
36
|
+
conn.shard = current_shard
|
|
63
37
|
end
|
|
64
38
|
|
|
65
39
|
private
|
|
66
40
|
|
|
41
|
+
def current_shard
|
|
42
|
+
::Rails.version < '7.0' ? connection_klass.current_switchman_shard : connection_class.current_switchman_shard
|
|
43
|
+
end
|
|
44
|
+
|
|
67
45
|
def tls_key
|
|
68
46
|
"#{object_id}_shard".to_sym
|
|
69
47
|
end
|
|
@@ -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
|
|
@@ -14,16 +14,19 @@ module Switchman
|
|
|
14
14
|
|
|
15
15
|
def connection
|
|
16
16
|
conn = super
|
|
17
|
-
::ActiveRecord::Base.connection_pool.switch_database(conn) if conn.shard != ::ActiveRecord::Base.
|
|
17
|
+
::ActiveRecord::Base.connection_pool.switch_database(conn) if conn.shard != ::ActiveRecord::Base.current_switchman_shard
|
|
18
18
|
conn
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
module Migrator
|
|
23
|
-
# significant change:
|
|
23
|
+
# significant change: just return MIGRATOR_SALT directly
|
|
24
|
+
# especially if you're going through pgbouncer, the database
|
|
25
|
+
# name you're accessing may not be consistent. it is NOT allowed
|
|
26
|
+
# to run migrations against multiple shards in the same database
|
|
27
|
+
# concurrently
|
|
24
28
|
def generate_migrator_advisory_lock_id
|
|
25
|
-
|
|
26
|
-
::ActiveRecord::Migrator::MIGRATOR_SALT * shard_name_hash
|
|
29
|
+
::ActiveRecord::Migrator::MIGRATOR_SALT
|
|
27
30
|
end
|
|
28
31
|
|
|
29
32
|
# significant change: strip out prefer_secondary from config
|
|
@@ -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,11 +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
|
+
end
|
|
14
|
+
|
|
15
|
+
def delete
|
|
16
|
+
db = shard.database_server
|
|
17
|
+
db.unguard { super }
|
|
13
18
|
end
|
|
14
19
|
end
|
|
15
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}\""
|
|
@@ -32,7 +32,7 @@ module Switchman
|
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
# copy/paste; use quote_local_table_name
|
|
35
|
-
def drop_database(name)
|
|
35
|
+
def drop_database(name) # :nodoc:
|
|
36
36
|
execute "DROP DATABASE IF EXISTS #{quote_local_table_name(name)}"
|
|
37
37
|
end
|
|
38
38
|
|
|
@@ -87,6 +87,10 @@ module Switchman
|
|
|
87
87
|
name.quoted
|
|
88
88
|
end
|
|
89
89
|
|
|
90
|
+
def with_global_table_name(&block)
|
|
91
|
+
with_local_table_name(false, &block)
|
|
92
|
+
end
|
|
93
|
+
|
|
90
94
|
def with_local_table_name(enable = true) # rubocop:disable Style/OptionalBooleanParameter
|
|
91
95
|
old_value = @use_local_table_name
|
|
92
96
|
@use_local_table_name = enable
|
|
@@ -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)
|
|
@@ -239,6 +242,10 @@ module Switchman
|
|
|
239
242
|
connection.with_local_table_name { super }
|
|
240
243
|
end
|
|
241
244
|
|
|
245
|
+
def table_name_matches?(from)
|
|
246
|
+
connection.with_global_table_name { super }
|
|
247
|
+
end
|
|
248
|
+
|
|
242
249
|
def transpose_predicates(predicates,
|
|
243
250
|
source_shard,
|
|
244
251
|
target_shard,
|
|
@@ -266,11 +273,11 @@ module Switchman
|
|
|
266
273
|
right_node = or_expr.right
|
|
267
274
|
new_left_predicates = transpose_single_predicate(left_node, source_shard,
|
|
268
275
|
target_shard, remove_nonlocal_primary_keys: remove_nonlocal_primary_keys)
|
|
269
|
-
or_expr.instance_variable_set(:@left, new_left_predicates) if new_left_predicates != left_node
|
|
270
276
|
new_right_predicates = transpose_single_predicate(right_node, source_shard,
|
|
271
277
|
target_shard, remove_nonlocal_primary_keys: remove_nonlocal_primary_keys)
|
|
272
|
-
|
|
273
|
-
|
|
278
|
+
return predicate if new_left_predicates == left_node && new_right_predicates == right_node
|
|
279
|
+
|
|
280
|
+
return ::Arel::Nodes::Grouping.new ::Arel::Nodes::Or.new(new_left_predicates, new_right_predicates)
|
|
274
281
|
end
|
|
275
282
|
return predicate unless predicate.is_a?(::Arel::Nodes::Binary) || predicate.is_a?(::Arel::Nodes::HomogeneousIn)
|
|
276
283
|
return predicate unless predicate.left.is_a?(::Arel::Attributes::Attribute)
|
|
@@ -287,7 +294,7 @@ module Switchman
|
|
|
287
294
|
if source_shard
|
|
288
295
|
source_shard
|
|
289
296
|
elsif type == :primary
|
|
290
|
-
Shard.current(klass.
|
|
297
|
+
Shard.current(klass.connection_class_for_self)
|
|
291
298
|
elsif type == :foreign
|
|
292
299
|
source_shard_for_foreign_key(relation, column)
|
|
293
300
|
end
|
|
@@ -328,8 +335,9 @@ module Switchman
|
|
|
328
335
|
end
|
|
329
336
|
|
|
330
337
|
def transpose_predicate_value(value, current_shard, target_shard, attribute_type, remove_non_local_ids)
|
|
331
|
-
|
|
332
|
-
|
|
338
|
+
case value
|
|
339
|
+
when ::Arel::Nodes::BindParam, ::ActiveModel::Attribute
|
|
340
|
+
query_att = value.is_a?(::ActiveModel::Attribute) ? value : value.value
|
|
333
341
|
current_id = query_att.value_before_type_cast
|
|
334
342
|
if current_id.is_a?(::ActiveRecord::StatementCache::Substitute)
|
|
335
343
|
current_id.sharded = true # mark for transposition later
|
|
@@ -342,7 +350,12 @@ module Switchman
|
|
|
342
350
|
# make a new bind param
|
|
343
351
|
value
|
|
344
352
|
else
|
|
345
|
-
|
|
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
|
|
346
359
|
end
|
|
347
360
|
end
|
|
348
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
|