switchman 1.13.3 → 2.2.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/app/models/switchman/shard.rb +712 -11
- data/db/migrate/20130328212039_create_switchman_shards.rb +2 -0
- data/db/migrate/20130328224244_create_default_shard.rb +3 -1
- data/db/migrate/20161206323434_add_back_default_string_limits_switchman.rb +2 -0
- data/db/migrate/20180828183945_add_default_shard_index.rb +3 -1
- data/db/migrate/20180828192111_add_timestamps_to_shards.rb +2 -0
- data/db/migrate/20190114212900_add_unique_name_indexes.rb +2 -0
- data/lib/switchman/action_controller/caching.rb +2 -0
- data/lib/switchman/active_record/abstract_adapter.rb +6 -4
- data/lib/switchman/active_record/association.rb +45 -16
- data/lib/switchman/active_record/attribute_methods.rb +43 -17
- data/lib/switchman/active_record/base.rb +60 -20
- data/lib/switchman/active_record/batches.rb +2 -0
- data/lib/switchman/active_record/calculations.rb +5 -2
- data/lib/switchman/active_record/connection_handler.rb +48 -25
- data/lib/switchman/active_record/connection_pool.rb +19 -15
- data/lib/switchman/active_record/finder_methods.rb +2 -0
- data/lib/switchman/active_record/log_subscriber.rb +10 -12
- data/lib/switchman/active_record/migration.rb +48 -2
- data/lib/switchman/active_record/model_schema.rb +3 -1
- data/lib/switchman/active_record/persistence.rb +13 -2
- data/lib/switchman/active_record/postgresql_adapter.rb +149 -139
- data/lib/switchman/active_record/predicate_builder.rb +3 -1
- data/lib/switchman/active_record/query_cache.rb +19 -107
- data/lib/switchman/active_record/query_methods.rb +25 -3
- data/lib/switchman/active_record/reflection.rb +21 -8
- data/lib/switchman/active_record/relation.rb +66 -10
- data/lib/switchman/active_record/spawn_methods.rb +2 -0
- data/lib/switchman/active_record/statement_cache.rb +6 -25
- data/lib/switchman/active_record/table_definition.rb +4 -2
- data/lib/switchman/active_record/type_caster.rb +2 -0
- data/lib/switchman/active_record/where_clause_factory.rb +2 -0
- data/lib/switchman/active_support/cache.rb +18 -0
- data/lib/switchman/arel.rb +2 -0
- data/lib/switchman/call_super.rb +2 -0
- data/lib/switchman/connection_pool_proxy.rb +44 -22
- data/lib/switchman/database_server.rb +34 -19
- data/lib/switchman/default_shard.rb +3 -0
- data/lib/switchman/engine.rb +20 -15
- data/lib/switchman/environment.rb +2 -0
- data/lib/switchman/errors.rb +2 -0
- data/lib/switchman/{shackles → guard_rail}/relation.rb +7 -5
- data/lib/switchman/{shackles.rb → guard_rail.rb} +6 -4
- data/lib/switchman/open4.rb +2 -0
- data/lib/switchman/r_spec_helper.rb +9 -7
- data/lib/switchman/rails.rb +2 -0
- data/lib/switchman/schema_cache.rb +11 -1
- data/lib/switchman/sharded_instrumenter.rb +3 -1
- data/lib/switchman/standard_error.rb +3 -1
- data/lib/switchman/test_helper.rb +7 -11
- data/lib/switchman/version.rb +3 -1
- data/lib/switchman.rb +5 -1
- data/lib/tasks/switchman.rake +24 -17
- metadata +53 -26
- data/app/models/switchman/shard_internal.rb +0 -714
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
class CreateDefaultShard < ActiveRecord::Migration[4.2]
|
|
2
4
|
def up
|
|
3
5
|
unless Switchman::Shard.default.is_a?(Switchman::Shard)
|
|
4
6
|
Switchman::Shard.reset_column_information
|
|
5
7
|
Switchman::Shard.create!(:default => true)
|
|
6
|
-
Switchman::Shard.default(true)
|
|
8
|
+
Switchman::Shard.default(reload: true)
|
|
7
9
|
end
|
|
8
10
|
end
|
|
9
11
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
class AddDefaultShardIndex < ActiveRecord::Migration[4.2]
|
|
2
4
|
def change
|
|
3
5
|
Switchman::Shard.where(default: nil).update_all(default: false)
|
|
@@ -8,6 +10,6 @@ class AddDefaultShardIndex < ActiveRecord::Migration[4.2]
|
|
|
8
10
|
else
|
|
9
11
|
{}
|
|
10
12
|
end
|
|
11
|
-
add_index :switchman_shards, :default, options
|
|
13
|
+
add_index :switchman_shards, :default, **options
|
|
12
14
|
end
|
|
13
15
|
end
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'switchman/sharded_instrumenter'
|
|
2
4
|
|
|
3
5
|
module Switchman
|
|
4
6
|
module ActiveRecord
|
|
5
7
|
module AbstractAdapter
|
|
6
8
|
module ForeignKeyCheck
|
|
7
|
-
def add_column(table, name, type,
|
|
8
|
-
Engine.foreign_key_check(name, type,
|
|
9
|
+
def add_column(table, name, type, limit: nil, **)
|
|
10
|
+
Engine.foreign_key_check(name, type, limit: limit)
|
|
9
11
|
super
|
|
10
12
|
end
|
|
11
13
|
end
|
|
@@ -27,8 +29,8 @@ module Switchman
|
|
|
27
29
|
quote_table_name(name)
|
|
28
30
|
end
|
|
29
31
|
|
|
30
|
-
def
|
|
31
|
-
|
|
32
|
+
def schema_migration
|
|
33
|
+
::ActiveRecord::SchemaMigration
|
|
32
34
|
end
|
|
33
35
|
|
|
34
36
|
protected
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Switchman
|
|
2
4
|
module ActiveRecord
|
|
3
5
|
module Association
|
|
@@ -30,17 +32,14 @@ module Switchman
|
|
|
30
32
|
end
|
|
31
33
|
|
|
32
34
|
module CollectionAssociation
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
Shard.with_each_shard(shards, [klass.shard_category, owner.class.shard_category].uniq) do
|
|
40
|
-
super
|
|
41
|
-
end
|
|
35
|
+
def find_target
|
|
36
|
+
shards = reflection.options[:multishard] && owner.respond_to?(:associated_shards) ? owner.associated_shards : [shard]
|
|
37
|
+
# activate both the owner and the target's shard category, so that Reflection#join_id_for,
|
|
38
|
+
# when called for the owner, will be returned relative to shard the query will execute on
|
|
39
|
+
Shard.with_each_shard(shards, [klass.shard_category, owner.class.shard_category].uniq) do
|
|
40
|
+
super
|
|
42
41
|
end
|
|
43
|
-
|
|
42
|
+
end
|
|
44
43
|
end
|
|
45
44
|
|
|
46
45
|
module BelongsToAssociation
|
|
@@ -76,16 +75,36 @@ module Switchman
|
|
|
76
75
|
|
|
77
76
|
module Preloader
|
|
78
77
|
module Association
|
|
79
|
-
if ::Rails.version >= "5.2"
|
|
78
|
+
if ::Rails.version >= "5.2" and ::Rails.version < "6.0"
|
|
80
79
|
def run(preloader)
|
|
81
|
-
|
|
82
|
-
associated_records_by_owner(preloader).each do |owner, records|
|
|
80
|
+
associated_records_by_owner.each do |owner, records|
|
|
83
81
|
associate_records_to_owner(owner, records)
|
|
84
82
|
end
|
|
85
83
|
end
|
|
86
84
|
end
|
|
87
85
|
|
|
86
|
+
if ::Rails.version >= "6.0"
|
|
87
|
+
# Copypasta from Activerecord but with added global_id_for goodness.
|
|
88
|
+
def records_for(ids)
|
|
89
|
+
scope.where(association_key_name => ids).load do |record|
|
|
90
|
+
global_key = if model.shard_category == :unsharded
|
|
91
|
+
convert_key(record[association_key_name])
|
|
92
|
+
else
|
|
93
|
+
Shard.global_id_for(record[association_key_name], record.shard)
|
|
94
|
+
end
|
|
95
|
+
owner = owners_by_key[global_key.to_s].first
|
|
96
|
+
association = owner.association(reflection.name)
|
|
97
|
+
association.set_inverse_instance(record)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def records_by_owner
|
|
102
|
+
associated_records_by_owner
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
88
106
|
def associated_records_by_owner(preloader = nil)
|
|
107
|
+
return @associated_records_by_owner if defined?(@associated_records_by_owner)
|
|
89
108
|
owners_map = owners_by_key
|
|
90
109
|
|
|
91
110
|
if klass.nil? || owners_map.empty?
|
|
@@ -133,10 +152,13 @@ module Switchman
|
|
|
133
152
|
records.flatten!
|
|
134
153
|
end
|
|
135
154
|
|
|
155
|
+
# This ivar may look unused, but remember this is an extension of
|
|
156
|
+
# rails' AR::Associations::Preloader::Association class. It gets used
|
|
157
|
+
# by that class (and its subclasses).
|
|
136
158
|
@preloaded_records = records
|
|
137
159
|
|
|
138
160
|
# Each record may have multiple owners, and vice-versa
|
|
139
|
-
|
|
161
|
+
@associated_records_by_owner = owners.each_with_object({}) do |owner,h|
|
|
140
162
|
h[owner] = []
|
|
141
163
|
end
|
|
142
164
|
records.each do |record|
|
|
@@ -145,10 +167,10 @@ module Switchman
|
|
|
145
167
|
|
|
146
168
|
owners_map[owner_key.to_s].each do |owner|
|
|
147
169
|
owner.association(reflection.name).set_inverse_instance(record)
|
|
148
|
-
|
|
170
|
+
@associated_records_by_owner[owner] << record
|
|
149
171
|
end
|
|
150
172
|
end
|
|
151
|
-
|
|
173
|
+
@associated_records_by_owner
|
|
152
174
|
end
|
|
153
175
|
|
|
154
176
|
def owners_by_key
|
|
@@ -183,6 +205,13 @@ module Switchman
|
|
|
183
205
|
(record.has_attribute?(reflection.foreign_key) && record.send(reflection.foreign_key) != key) || # have to use send instead of [] because sharding
|
|
184
206
|
record.attribute_changed?(reflection.foreign_key)
|
|
185
207
|
end
|
|
208
|
+
|
|
209
|
+
def save_belongs_to_association(reflection)
|
|
210
|
+
# this seems counter-intuitive, but the autosave code will assign to attribute bypassing switchman,
|
|
211
|
+
# after reading the id attribute _without_ bypassing switchman. So we need Shard.current for the
|
|
212
|
+
# category of the associated record to match Shard.current for the category of self
|
|
213
|
+
shard.activate(shard_category_for_reflection(reflection)) { super }
|
|
214
|
+
end
|
|
186
215
|
end
|
|
187
216
|
end
|
|
188
217
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Switchman
|
|
2
4
|
module ActiveRecord
|
|
3
5
|
module AttributeMethods
|
|
@@ -36,11 +38,15 @@ module Switchman
|
|
|
36
38
|
def define_method_global_attribute(attr_name)
|
|
37
39
|
if sharded_column?(attr_name)
|
|
38
40
|
generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
39
|
-
def
|
|
41
|
+
def __temp_global_attribute__
|
|
42
|
+
raw_value = original_#{attr_name}
|
|
43
|
+
return nil if raw_value.nil?
|
|
44
|
+
return raw_value if raw_value > Shard::IDS_PER_SHARD
|
|
45
|
+
|
|
40
46
|
Shard.global_id_for(original_#{attr_name}, shard)
|
|
41
47
|
end
|
|
42
|
-
alias_method 'global_#{attr_name}', :
|
|
43
|
-
undef_method :
|
|
48
|
+
alias_method 'global_#{attr_name}', :__temp_global_attribute__
|
|
49
|
+
undef_method :__temp_global_attribute__
|
|
44
50
|
RUBY
|
|
45
51
|
else
|
|
46
52
|
define_method_unsharded_column(attr_name, 'global')
|
|
@@ -50,17 +56,22 @@ module Switchman
|
|
|
50
56
|
def define_method_local_attribute(attr_name)
|
|
51
57
|
if sharded_column?(attr_name)
|
|
52
58
|
generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
53
|
-
def
|
|
54
|
-
|
|
59
|
+
def __temp_local_attribute__
|
|
60
|
+
raw_value = original_#{attr_name}
|
|
61
|
+
return nil if raw_value.nil?
|
|
62
|
+
return raw_value % Shard::IDS_PER_SHARD
|
|
55
63
|
end
|
|
56
|
-
alias_method 'local_#{attr_name}', :
|
|
57
|
-
undef_method :
|
|
64
|
+
alias_method 'local_#{attr_name}', :__temp_local_attribute__
|
|
65
|
+
undef_method :__temp_local_attribute__
|
|
58
66
|
RUBY
|
|
59
67
|
else
|
|
60
68
|
define_method_unsharded_column(attr_name, 'local')
|
|
61
69
|
end
|
|
62
70
|
end
|
|
63
71
|
|
|
72
|
+
# see also Base#shard_category_for_reflection
|
|
73
|
+
# the difference being this will output static strings for the common cases, making them
|
|
74
|
+
# more performant
|
|
64
75
|
def shard_category_code_for_reflection(reflection)
|
|
65
76
|
if reflection
|
|
66
77
|
if reflection.options[:polymorphic]
|
|
@@ -89,18 +100,33 @@ module Switchman
|
|
|
89
100
|
# rename the original method to original_
|
|
90
101
|
alias_method 'original_#{attr_name}', '#{attr_name}'
|
|
91
102
|
# and replace with one that transposes the id
|
|
92
|
-
def
|
|
93
|
-
|
|
103
|
+
def __temp_relative_attribute__
|
|
104
|
+
raw_value = original_#{attr_name}
|
|
105
|
+
return nil if raw_value.nil?
|
|
106
|
+
|
|
107
|
+
abs_raw_value = raw_value.abs
|
|
108
|
+
current_shard = Shard.current(#{shard_category_code_for_reflection(reflection)})
|
|
109
|
+
same_shard = shard == current_shard
|
|
110
|
+
return raw_value if same_shard && abs_raw_value < Shard::IDS_PER_SHARD
|
|
111
|
+
|
|
112
|
+
value_shard_id = abs_raw_value / Shard::IDS_PER_SHARD
|
|
113
|
+
# this is a stupid case when someone stuffed a global id for the current shard in instead
|
|
114
|
+
# of a local id
|
|
115
|
+
return raw_value % Shard::IDS_PER_SHARD if value_shard_id == current_shard.id
|
|
116
|
+
return raw_value if !same_shard && abs_raw_value > Shard::IDS_PER_SHARD
|
|
117
|
+
return shard.global_id_for(raw_value) if !same_shard && abs_raw_value < Shard::IDS_PER_SHARD
|
|
118
|
+
|
|
119
|
+
Shard.relative_id_for(raw_value, shard, current_shard)
|
|
94
120
|
end
|
|
95
|
-
alias_method '#{attr_name}', :
|
|
96
|
-
undef_method :
|
|
121
|
+
alias_method '#{attr_name}', :__temp_relative_attribute__
|
|
122
|
+
undef_method :__temp_relative_attribute__
|
|
97
123
|
|
|
98
124
|
alias_method 'original_#{attr_name}=', '#{attr_name}='
|
|
99
|
-
def
|
|
125
|
+
def __temp_relative_attribute_assignment__(new_value)
|
|
100
126
|
self.original_#{attr_name} = Shard.relative_id_for(new_value, Shard.current(#{shard_category_code_for_reflection(reflection)}), shard)
|
|
101
127
|
end
|
|
102
|
-
alias_method '#{attr_name}=', :
|
|
103
|
-
undef_method :
|
|
128
|
+
alias_method '#{attr_name}=', :__temp_relative_attribute_assignment__
|
|
129
|
+
undef_method :__temp_relative_attribute_assignment__
|
|
104
130
|
RUBY
|
|
105
131
|
else
|
|
106
132
|
define_method_unsharded_column(attr_name, 'global')
|
|
@@ -110,11 +136,11 @@ module Switchman
|
|
|
110
136
|
def define_method_unsharded_column(attr_name, prefix)
|
|
111
137
|
return if columns_hash["#{prefix}_#{attr_name}"]
|
|
112
138
|
generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
113
|
-
def
|
|
139
|
+
def __temp_unsharded_attribute__
|
|
114
140
|
raise NoMethodError, "undefined method `#{prefix}_#{attr_name}'; are you missing an association?"
|
|
115
141
|
end
|
|
116
|
-
alias_method '#{prefix}_#{attr_name}', :
|
|
117
|
-
undef_method :
|
|
142
|
+
alias_method '#{prefix}_#{attr_name}', :__temp_unsharded_attribute__
|
|
143
|
+
undef_method :__temp_unsharded_attribute__
|
|
118
144
|
RUBY
|
|
119
145
|
end
|
|
120
146
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Switchman
|
|
2
4
|
module ActiveRecord
|
|
3
5
|
module Base
|
|
@@ -31,20 +33,20 @@ module Switchman
|
|
|
31
33
|
@integral_id
|
|
32
34
|
end
|
|
33
35
|
|
|
34
|
-
def transaction(
|
|
36
|
+
def transaction(**)
|
|
35
37
|
if self != ::ActiveRecord::Base && current_scope
|
|
36
38
|
current_scope.activate do
|
|
37
39
|
db = Shard.current(shard_category).database_server
|
|
38
|
-
if ::
|
|
39
|
-
db.
|
|
40
|
+
if ::GuardRail.environment != db.guard_rail_environment
|
|
41
|
+
db.unguard { super }
|
|
40
42
|
else
|
|
41
43
|
super
|
|
42
44
|
end
|
|
43
45
|
end
|
|
44
46
|
else
|
|
45
47
|
db = Shard.current(shard_category).database_server
|
|
46
|
-
if ::
|
|
47
|
-
db.
|
|
48
|
+
if ::GuardRail.environment != db.guard_rail_environment
|
|
49
|
+
db.unguard { super }
|
|
48
50
|
else
|
|
49
51
|
super
|
|
50
52
|
end
|
|
@@ -68,21 +70,29 @@ module Switchman
|
|
|
68
70
|
result
|
|
69
71
|
end
|
|
70
72
|
end
|
|
71
|
-
end
|
|
72
73
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if self.class.sharded_primary_key?
|
|
78
|
-
@shard = Shard.shard_for(self[self.class.primary_key], Shard.current(self.class.shard_category))
|
|
79
|
-
else
|
|
80
|
-
@shard = Shard.current(self.class.shard_category)
|
|
74
|
+
def clear_query_caches_for_current_thread
|
|
75
|
+
::ActiveRecord::Base.connection_handlers.each_value do |handler|
|
|
76
|
+
handler.connection_pool_list.each do |pool|
|
|
77
|
+
pool.connection(switch_shard: false).clear_query_cache if pool.active_connection?
|
|
81
78
|
end
|
|
82
79
|
end
|
|
83
80
|
end
|
|
84
81
|
end
|
|
85
82
|
|
|
83
|
+
def self.prepended(klass)
|
|
84
|
+
klass.singleton_class.prepend(ClassMethods)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def _run_initialize_callbacks
|
|
88
|
+
@shard ||= if self.class.sharded_primary_key?
|
|
89
|
+
Shard.shard_for(self[self.class.primary_key], Shard.current(self.class.shard_category))
|
|
90
|
+
else
|
|
91
|
+
Shard.current(self.class.shard_category)
|
|
92
|
+
end
|
|
93
|
+
super
|
|
94
|
+
end
|
|
95
|
+
|
|
86
96
|
def shard
|
|
87
97
|
@shard || Shard.current(self.class.shard_category) || Shard.default
|
|
88
98
|
end
|
|
@@ -97,14 +107,14 @@ module Switchman
|
|
|
97
107
|
end
|
|
98
108
|
end
|
|
99
109
|
|
|
100
|
-
def save(
|
|
110
|
+
def save(*, **)
|
|
101
111
|
@shard_set_in_stone = true
|
|
102
|
-
self.class.shard(shard, :implicit).scoping { super }
|
|
112
|
+
(self.class.current_scope || self.class.default_scoped).shard(shard, :implicit).scoping { super }
|
|
103
113
|
end
|
|
104
114
|
|
|
105
|
-
def save!(
|
|
115
|
+
def save!(*, **)
|
|
106
116
|
@shard_set_in_stone = true
|
|
107
|
-
self.class.shard(shard, :implicit).scoping { super }
|
|
117
|
+
(self.class.current_scope || self.class.default_scoped).shard(shard, :implicit).scoping { super }
|
|
108
118
|
end
|
|
109
119
|
|
|
110
120
|
def destroy
|
|
@@ -120,9 +130,9 @@ module Switchman
|
|
|
120
130
|
result
|
|
121
131
|
end
|
|
122
132
|
|
|
123
|
-
def transaction(
|
|
133
|
+
def transaction(**kwargs, &block)
|
|
124
134
|
shard.activate(self.class.shard_category) do
|
|
125
|
-
self.class.transaction(
|
|
135
|
+
self.class.transaction(**kwargs, &block)
|
|
126
136
|
end
|
|
127
137
|
end
|
|
128
138
|
|
|
@@ -146,6 +156,36 @@ module Switchman
|
|
|
146
156
|
# do this the Rails 4.2 way, so that if Shard.current != self.shard, the id gets transposed
|
|
147
157
|
self.class.connection.quote(id)
|
|
148
158
|
end
|
|
159
|
+
|
|
160
|
+
def update_columns(*)
|
|
161
|
+
db = shard.database_server
|
|
162
|
+
if ::GuardRail.environment != db.guard_rail_environment
|
|
163
|
+
return db.unguard { super }
|
|
164
|
+
else
|
|
165
|
+
super
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
protected
|
|
170
|
+
|
|
171
|
+
# see also AttributeMethods#shard_category_code_for_reflection
|
|
172
|
+
def shard_category_for_reflection(reflection)
|
|
173
|
+
if reflection
|
|
174
|
+
if reflection.options[:polymorphic]
|
|
175
|
+
begin
|
|
176
|
+
read_attribute(reflection.foreign_type)&.constantize&.shard_category || :primary
|
|
177
|
+
rescue NameError
|
|
178
|
+
# in case someone is abusing foreign_type to not point to an actual class
|
|
179
|
+
:primary
|
|
180
|
+
end
|
|
181
|
+
else
|
|
182
|
+
# otherwise we can just return a symbol for the statically known type of the association
|
|
183
|
+
reflection.klass.shard_category
|
|
184
|
+
end
|
|
185
|
+
else
|
|
186
|
+
shard_category
|
|
187
|
+
end
|
|
188
|
+
end
|
|
149
189
|
end
|
|
150
190
|
end
|
|
151
191
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Switchman
|
|
2
4
|
module ActiveRecord
|
|
3
5
|
module Calculations
|
|
@@ -49,7 +51,7 @@ module Switchman
|
|
|
49
51
|
|
|
50
52
|
def calculate_simple_average(column_name, distinct)
|
|
51
53
|
# See activerecord#execute_simple_calculation
|
|
52
|
-
relation =
|
|
54
|
+
relation = except(:order)
|
|
53
55
|
column = aggregate_column(column_name)
|
|
54
56
|
relation.select_values = [operation_over_aggregate_column(column, "average", distinct).as("average"),
|
|
55
57
|
operation_over_aggregate_column(column, "count", distinct).as("count")]
|
|
@@ -121,7 +123,8 @@ module Switchman
|
|
|
121
123
|
group_fields = group_attrs
|
|
122
124
|
end
|
|
123
125
|
|
|
124
|
-
|
|
126
|
+
# to_s is because Rails 5 returns a string but Rails 6 returns a symbol.
|
|
127
|
+
group_aliases = group_fields.map { |field| column_alias_for(field.downcase.to_s).to_s }
|
|
125
128
|
group_columns = group_aliases.zip(group_fields).map { |aliaz, field|
|
|
126
129
|
[aliaz, type_for(field), column_name_for(field)]
|
|
127
130
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'switchman/connection_pool_proxy'
|
|
2
4
|
|
|
3
5
|
module Switchman
|
|
4
6
|
module ActiveRecord
|
|
5
7
|
module ConnectionHandler
|
|
6
8
|
def self.make_sharing_automagic(config, shard = Shard.current)
|
|
7
|
-
key = config[:adapter] == 'postgresql' ? :schema_search_path : :database
|
|
8
|
-
|
|
9
9
|
# only load the shard name from the db if we have to
|
|
10
10
|
if !config[:shard_name]
|
|
11
11
|
# we may not be able to connect to this shard yet, cause it might be an empty database server
|
|
@@ -15,15 +15,13 @@ module Switchman
|
|
|
15
15
|
|
|
16
16
|
config[:shard_name] ||= shard_name
|
|
17
17
|
end
|
|
18
|
-
|
|
19
|
-
if !config[key] || config[key] == shard_name
|
|
20
|
-
# this may truncate the schema_search_path if it was not specified in database.yml
|
|
21
|
-
# but that's what our old behavior was anyway, so I guess it's okay
|
|
22
|
-
config[key] = '%{shard_name}'
|
|
23
|
-
end
|
|
24
18
|
end
|
|
25
19
|
|
|
26
20
|
def establish_connection(spec)
|
|
21
|
+
# Just skip establishing a sharded connection if sharding isn't loaded; we'll do it again later
|
|
22
|
+
# This only can happen when loading ActiveRecord::Base; after everything is loaded Shard will
|
|
23
|
+
# be defined and this will actually establish a connection
|
|
24
|
+
return unless defined?(Shard)
|
|
27
25
|
pool = super
|
|
28
26
|
|
|
29
27
|
# this is the first place that the adapter would have been required; but now we
|
|
@@ -39,19 +37,27 @@ module Switchman
|
|
|
39
37
|
# to sharding will recurse onto itself trying to access column information
|
|
40
38
|
Shard.default
|
|
41
39
|
|
|
40
|
+
config = pool.spec.config
|
|
42
41
|
# automatically change config to allow for sharing connections with simple config
|
|
43
|
-
config = ::Rails.version < '5.1' ? spec.config : pool.spec.config
|
|
44
42
|
ConnectionHandler.make_sharing_automagic(config)
|
|
45
43
|
ConnectionHandler.make_sharing_automagic(Shard.default.database_server.config)
|
|
46
44
|
|
|
47
|
-
if ::Rails.version < '
|
|
48
|
-
::ActiveRecord::Base.configurations[::Rails.env] = spec.instance_variable_get(:@config).stringify_keys
|
|
49
|
-
else
|
|
45
|
+
if ::Rails.version < '6.0'
|
|
50
46
|
::ActiveRecord::Base.configurations[::Rails.env] = config.stringify_keys
|
|
47
|
+
else
|
|
48
|
+
# Adopted from the deprecated code that currently lives in rails proper
|
|
49
|
+
remaining_configs = ::ActiveRecord::Base.configurations.configurations.reject { |db_config| db_config.env_name == ::Rails.env }
|
|
50
|
+
new_config = ::ActiveRecord::DatabaseConfigurations.new(::Rails.env => config.stringify_keys).configurations
|
|
51
|
+
new_configs = remaining_configs + new_config
|
|
52
|
+
|
|
53
|
+
::ActiveRecord::Base.configurations = new_configs
|
|
51
54
|
end
|
|
55
|
+
else
|
|
56
|
+
# this is probably wrong now
|
|
57
|
+
Shard.default.remove_instance_variable(:@name) if Shard.default.instance_variable_defined?(:@name)
|
|
52
58
|
end
|
|
53
59
|
|
|
54
|
-
@shard_connection_pools ||= { [:
|
|
60
|
+
@shard_connection_pools ||= { [:primary, Shard.default.database_server.shareable? ? ::Rails.env : Shard.default] => pool}
|
|
55
61
|
|
|
56
62
|
category = pool.spec.name.to_sym
|
|
57
63
|
proxy = ConnectionPoolProxy.new(category,
|
|
@@ -60,13 +66,13 @@ module Switchman
|
|
|
60
66
|
owner_to_pool[pool.spec.name] = proxy
|
|
61
67
|
|
|
62
68
|
if first_time
|
|
63
|
-
if Shard.default.database_server.config[:
|
|
64
|
-
Shard.default.database_server.
|
|
69
|
+
if Shard.default.database_server.config[:prefer_secondary]
|
|
70
|
+
Shard.default.database_server.guard!
|
|
65
71
|
end
|
|
66
72
|
|
|
67
|
-
if Shard.default.is_a?(DefaultShard) && Shard.default.database_server.config[:
|
|
68
|
-
Shard.default.database_server.
|
|
69
|
-
Shard.default(true)
|
|
73
|
+
if Shard.default.is_a?(DefaultShard) && Shard.default.database_server.config[:secondary]
|
|
74
|
+
Shard.default.database_server.guard!
|
|
75
|
+
Shard.default(reload: true)
|
|
70
76
|
end
|
|
71
77
|
end
|
|
72
78
|
|
|
@@ -106,8 +112,20 @@ module Switchman
|
|
|
106
112
|
end
|
|
107
113
|
|
|
108
114
|
def remove_connection(spec_name)
|
|
115
|
+
# also remove pools based on the same spec name that are for shard category purposes
|
|
116
|
+
# can't just use delete_if, because it's a Concurrent::Map, not a Hash
|
|
117
|
+
owner_to_pool.keys.each do |k|
|
|
118
|
+
next if k == spec_name
|
|
119
|
+
|
|
120
|
+
v = owner_to_pool[k]
|
|
121
|
+
owner_to_pool.delete(k) if v.is_a?(ConnectionPoolProxy) && v.default_pool.spec.name == spec_name
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# unwrap the pool from inside a ConnectionPoolProxy
|
|
109
125
|
pool = owner_to_pool[spec_name]
|
|
110
126
|
owner_to_pool[spec_name] = pool.default_pool if pool.is_a?(ConnectionPoolProxy)
|
|
127
|
+
|
|
128
|
+
# now let Rails do its thing with the data type it expects
|
|
111
129
|
super
|
|
112
130
|
end
|
|
113
131
|
|
|
@@ -122,16 +140,21 @@ module Switchman
|
|
|
122
140
|
else
|
|
123
141
|
ancestor_pool.spec
|
|
124
142
|
end
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
143
|
+
# avoid copying "duplicate" pools that implement shard categories.
|
|
144
|
+
# they'll have a spec.name of primary, but a spec_name of something else, like unsharded
|
|
145
|
+
if spec.name == spec_name
|
|
146
|
+
pool = establish_connection(spec.to_hash)
|
|
147
|
+
pool.instance_variable_set(:@schema_cache, ancestor_pool.schema_cache) if ancestor_pool.schema_cache
|
|
148
|
+
next pool
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
if spec_name != "primary"
|
|
130
153
|
primary_pool = retrieve_connection_pool("primary")
|
|
131
154
|
if primary_pool.is_a?(ConnectionPoolProxy)
|
|
132
155
|
pool = ConnectionPoolProxy.new(spec_name.to_sym, primary_pool.default_pool, @shard_connection_pools)
|
|
133
|
-
pool.schema_cache.
|
|
134
|
-
pool
|
|
156
|
+
pool.schema_cache.copy_references(primary_pool.schema_cache)
|
|
157
|
+
owner_to_pool[spec_name] = pool
|
|
135
158
|
else
|
|
136
159
|
primary_pool
|
|
137
160
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'switchman/errors'
|
|
2
4
|
|
|
3
5
|
module Switchman
|
|
@@ -32,10 +34,10 @@ module Switchman
|
|
|
32
34
|
conn
|
|
33
35
|
end
|
|
34
36
|
|
|
35
|
-
def connection
|
|
36
|
-
conn = super
|
|
37
|
+
def connection(switch_shard: true)
|
|
38
|
+
conn = super()
|
|
37
39
|
raise NonExistentShardError if shard.new_record?
|
|
38
|
-
switch_database(conn) if conn.shard != self.shard
|
|
40
|
+
switch_database(conn) if conn.shard != self.shard && switch_shard
|
|
39
41
|
conn
|
|
40
42
|
end
|
|
41
43
|
|
|
@@ -47,6 +49,20 @@ module Switchman
|
|
|
47
49
|
end
|
|
48
50
|
end
|
|
49
51
|
|
|
52
|
+
def remove_shard!(shard)
|
|
53
|
+
synchronize do
|
|
54
|
+
# The shard might be currently active, so we need to update our own shard
|
|
55
|
+
if self.shard == shard
|
|
56
|
+
self.shard = Shard.default
|
|
57
|
+
end
|
|
58
|
+
# Update out any connections that may be using this shard
|
|
59
|
+
@connections.each do |conn|
|
|
60
|
+
# This will also update the connection's shard to the default shard
|
|
61
|
+
switch_database(conn) if conn.shard == shard
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
50
66
|
def clear_idle_connections!(since_when)
|
|
51
67
|
synchronize do
|
|
52
68
|
@connections.reject! do |conn|
|
|
@@ -70,18 +86,6 @@ module Switchman
|
|
|
70
86
|
end
|
|
71
87
|
|
|
72
88
|
spec.config[:shard_name] = self.shard.name
|
|
73
|
-
case conn.adapter_name
|
|
74
|
-
when 'MySQL', 'Mysql2'
|
|
75
|
-
conn.execute("USE #{spec.config[:database]}")
|
|
76
|
-
when 'PostgreSQL'
|
|
77
|
-
if conn.schema_search_path != spec.config[:schema_search_path]
|
|
78
|
-
conn.schema_search_path = spec.config[:schema_search_path]
|
|
79
|
-
end
|
|
80
|
-
when 'SQLite'
|
|
81
|
-
# This is an artifact of the adapter modifying the path to be an absolute path when it is instantiated; just let it slide
|
|
82
|
-
else
|
|
83
|
-
raise("Cannot switch databases on same DatabaseServer with adapter type: #{conn.adapter_name}. Limit one Shard per DatabaseServer.")
|
|
84
|
-
end
|
|
85
89
|
conn.shard = shard
|
|
86
90
|
end
|
|
87
91
|
|