switchman 1.5.21 → 2.1.5
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 +5 -5
- data/app/models/switchman/shard.rb +757 -11
- data/db/migrate/20130328212039_create_switchman_shards.rb +3 -1
- data/db/migrate/20130328224244_create_default_shard.rb +4 -2
- data/db/migrate/20161206323434_add_back_default_string_limits_switchman.rb +13 -0
- data/db/migrate/20180828183945_add_default_shard_index.rb +15 -0
- data/db/migrate/20180828192111_add_timestamps_to_shards.rb +17 -0
- data/db/migrate/20190114212900_add_unique_name_indexes.rb +9 -0
- data/lib/switchman/action_controller/caching.rb +2 -0
- data/lib/switchman/active_record/abstract_adapter.rb +14 -4
- data/lib/switchman/active_record/association.rb +64 -37
- data/lib/switchman/active_record/attribute_methods.rb +54 -22
- data/lib/switchman/active_record/base.rb +76 -31
- data/lib/switchman/active_record/batches.rb +3 -1
- data/lib/switchman/active_record/calculations.rb +17 -22
- data/lib/switchman/active_record/connection_handler.rb +88 -78
- data/lib/switchman/active_record/connection_pool.rb +28 -23
- data/lib/switchman/active_record/finder_methods.rb +37 -28
- data/lib/switchman/active_record/log_subscriber.rb +14 -19
- data/lib/switchman/active_record/migration.rb +80 -0
- data/lib/switchman/active_record/model_schema.rb +3 -1
- data/lib/switchman/active_record/persistence.rb +9 -1
- data/lib/switchman/active_record/postgresql_adapter.rb +170 -126
- data/lib/switchman/active_record/predicate_builder.rb +3 -1
- data/lib/switchman/active_record/query_cache.rb +22 -87
- data/lib/switchman/active_record/query_methods.rb +139 -125
- data/lib/switchman/active_record/reflection.rb +42 -14
- data/lib/switchman/active_record/relation.rb +108 -33
- data/lib/switchman/active_record/spawn_methods.rb +2 -0
- data/lib/switchman/active_record/statement_cache.rb +44 -52
- 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 +5 -2
- data/lib/switchman/active_support/cache.rb +18 -0
- data/lib/switchman/arel.rb +8 -25
- data/lib/switchman/call_super.rb +19 -0
- data/lib/switchman/connection_pool_proxy.rb +70 -24
- data/lib/switchman/database_server.rb +69 -59
- data/lib/switchman/default_shard.rb +3 -0
- data/lib/switchman/engine.rb +44 -41
- 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 +14 -8
- data/lib/switchman/rails.rb +2 -0
- data/lib/switchman/schema_cache.rb +17 -0
- data/lib/switchman/sharded_instrumenter.rb +4 -2
- data/lib/switchman/standard_error.rb +4 -2
- data/lib/switchman/test_helper.rb +7 -10
- data/lib/switchman/version.rb +3 -1
- data/lib/switchman.rb +5 -1
- data/lib/tasks/switchman.rake +53 -72
- metadata +84 -38
- data/app/models/switchman/shard_internal.rb +0 -692
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
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
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddBackDefaultStringLimitsSwitchman < ActiveRecord::Migration[4.2]
|
|
4
|
+
def up
|
|
5
|
+
add_string_limit_if_missing :switchman_shards, :name
|
|
6
|
+
add_string_limit_if_missing :switchman_shards, :database_server_id
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def add_string_limit_if_missing(table, column)
|
|
10
|
+
return if column_exists?(table, column, :string, limit: 255)
|
|
11
|
+
change_column table, column, :string, limit: 255
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddDefaultShardIndex < ActiveRecord::Migration[4.2]
|
|
4
|
+
def change
|
|
5
|
+
Switchman::Shard.where(default: nil).update_all(default: false)
|
|
6
|
+
change_column_default :switchman_shards, :default, false
|
|
7
|
+
change_column_null :switchman_shards, :default, false
|
|
8
|
+
options = if connection.adapter_name == 'PostgreSQL'
|
|
9
|
+
{ unique: true, where: "\"default\"" }
|
|
10
|
+
else
|
|
11
|
+
{}
|
|
12
|
+
end
|
|
13
|
+
add_index :switchman_shards, :default, **options
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddTimestampsToShards < ActiveRecord::Migration[4.2]
|
|
4
|
+
def change
|
|
5
|
+
add_timestamps :switchman_shards, null: true
|
|
6
|
+
now = Time.now.utc
|
|
7
|
+
Switchman::Shard.update_all(updated_at: now, created_at: now) if Switchman::Shard.current.default?
|
|
8
|
+
change_column_null :switchman_shards, :updated_at, false
|
|
9
|
+
change_column_null :switchman_shards, :created_at, false
|
|
10
|
+
|
|
11
|
+
if Switchman::Shard.current.default?
|
|
12
|
+
Switchman::Shard.connection.schema_cache.clear!
|
|
13
|
+
Switchman::Shard.reset_column_information
|
|
14
|
+
Switchman::Shard.columns
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddUniqueNameIndexes < ActiveRecord::Migration[4.2]
|
|
4
|
+
def change
|
|
5
|
+
add_index :switchman_shards, [:database_server_id, :name], unique: true
|
|
6
|
+
add_index :switchman_shards, :database_server_id, unique: true, where: "name IS NULL", name: 'index_switchman_shards_unique_primary_shard'
|
|
7
|
+
add_index :switchman_shards, "(true)", unique: true, where: "database_server_id IS NULL AND name IS NULL", name: 'index_switchman_shards_unique_primary_db_and_shard'
|
|
8
|
+
end
|
|
9
|
+
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
|
|
@@ -38,6 +40,14 @@ module Switchman
|
|
|
38
40
|
ensure
|
|
39
41
|
@last_query_at = Time.now
|
|
40
42
|
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def id_value_for_database(value)
|
|
47
|
+
return super unless value.class.sharded_primary_key?
|
|
48
|
+
# do this the Rails 4.2 way, so that if Shard.current != self.shard, the id gets transposed
|
|
49
|
+
quote(value.id)
|
|
50
|
+
end
|
|
41
51
|
end
|
|
42
52
|
end
|
|
43
53
|
end
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Switchman
|
|
2
4
|
module ActiveRecord
|
|
3
5
|
module Association
|
|
4
6
|
def shard
|
|
5
|
-
|
|
6
|
-
# polymorphic associations assume the same shard as the owning item
|
|
7
|
-
@owner.shard
|
|
8
|
-
else
|
|
9
|
-
Shard.default
|
|
10
|
-
end
|
|
7
|
+
reflection.shard(owner)
|
|
11
8
|
end
|
|
12
9
|
|
|
13
10
|
def build_record(*args)
|
|
@@ -18,12 +15,6 @@ module Switchman
|
|
|
18
15
|
self.shard.activate { super }
|
|
19
16
|
end
|
|
20
17
|
|
|
21
|
-
def association_scope
|
|
22
|
-
if klass
|
|
23
|
-
shard.activate(klass.shard_category) { super }
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
|
|
27
18
|
def scope
|
|
28
19
|
shard_value = @reflection.options[:multishard] ? @owner : self.shard
|
|
29
20
|
@owner.shard.activate { super.shard(shard_value, :association) }
|
|
@@ -41,9 +32,11 @@ module Switchman
|
|
|
41
32
|
end
|
|
42
33
|
|
|
43
34
|
module CollectionAssociation
|
|
44
|
-
def
|
|
45
|
-
shards = reflection.options[:multishard] && owner.respond_to?(:associated_shards) ? owner.associated_shards : [
|
|
46
|
-
|
|
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
|
|
47
40
|
super
|
|
48
41
|
end
|
|
49
42
|
end
|
|
@@ -69,30 +62,49 @@ module Switchman
|
|
|
69
62
|
end
|
|
70
63
|
end
|
|
71
64
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
def self.build(_model, _reflection)
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def self.valid_options
|
|
78
|
-
[:multishard]
|
|
79
|
-
end
|
|
65
|
+
module Extension
|
|
66
|
+
def self.build(_model, _reflection)
|
|
80
67
|
end
|
|
81
68
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
module Builder
|
|
85
|
-
module CollectionAssociation
|
|
86
|
-
def valid_options
|
|
87
|
-
super + [:multishard]
|
|
88
|
-
end
|
|
89
|
-
end
|
|
69
|
+
def self.valid_options
|
|
70
|
+
[:multishard]
|
|
90
71
|
end
|
|
91
72
|
end
|
|
92
73
|
|
|
74
|
+
::ActiveRecord::Associations::Builder::Association.extensions << Extension
|
|
75
|
+
|
|
93
76
|
module Preloader
|
|
94
77
|
module Association
|
|
78
|
+
if ::Rails.version >= "5.2" and ::Rails.version < "6.0"
|
|
79
|
+
def run(preloader)
|
|
80
|
+
associated_records_by_owner.each do |owner, records|
|
|
81
|
+
associate_records_to_owner(owner, records)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
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
|
+
|
|
95
106
|
def associated_records_by_owner(preloader = nil)
|
|
107
|
+
return @associated_records_by_owner if defined?(@associated_records_by_owner)
|
|
96
108
|
owners_map = owners_by_key
|
|
97
109
|
|
|
98
110
|
if klass.nil? || owners_map.empty?
|
|
@@ -140,12 +152,13 @@ module Switchman
|
|
|
140
152
|
records.flatten!
|
|
141
153
|
end
|
|
142
154
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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).
|
|
158
|
+
@preloaded_records = records
|
|
146
159
|
|
|
147
160
|
# Each record may have multiple owners, and vice-versa
|
|
148
|
-
|
|
161
|
+
@associated_records_by_owner = owners.each_with_object({}) do |owner,h|
|
|
149
162
|
h[owner] = []
|
|
150
163
|
end
|
|
151
164
|
records.each do |record|
|
|
@@ -153,10 +166,11 @@ module Switchman
|
|
|
153
166
|
owner_key = Shard.global_id_for(owner_key, record.shard) if owner_key && record.class.sharded_column?(association_key_name)
|
|
154
167
|
|
|
155
168
|
owners_map[owner_key.to_s].each do |owner|
|
|
156
|
-
|
|
169
|
+
owner.association(reflection.name).set_inverse_instance(record)
|
|
170
|
+
@associated_records_by_owner[owner] << record
|
|
157
171
|
end
|
|
158
172
|
end
|
|
159
|
-
|
|
173
|
+
@associated_records_by_owner
|
|
160
174
|
end
|
|
161
175
|
|
|
162
176
|
def owners_by_key
|
|
@@ -174,6 +188,12 @@ module Switchman
|
|
|
174
188
|
end
|
|
175
189
|
|
|
176
190
|
module CollectionProxy
|
|
191
|
+
def initialize(*args)
|
|
192
|
+
super
|
|
193
|
+
self.shard_value = scope.shard_value
|
|
194
|
+
self.shard_source_value = :association
|
|
195
|
+
end
|
|
196
|
+
|
|
177
197
|
def shard(*args)
|
|
178
198
|
scope.shard(*args)
|
|
179
199
|
end
|
|
@@ -185,6 +205,13 @@ module Switchman
|
|
|
185
205
|
(record.has_attribute?(reflection.foreign_key) && record.send(reflection.foreign_key) != key) || # have to use send instead of [] because sharding
|
|
186
206
|
record.attribute_changed?(reflection.foreign_key)
|
|
187
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
|
|
188
215
|
end
|
|
189
216
|
end
|
|
190
217
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Switchman
|
|
2
4
|
module ActiveRecord
|
|
3
5
|
module AttributeMethods
|
|
@@ -27,7 +29,7 @@ module Switchman
|
|
|
27
29
|
def reflection_for_integer_attribute(attr_name)
|
|
28
30
|
attr_name = attr_name.to_s
|
|
29
31
|
columns_hash[attr_name] && columns_hash[attr_name].type == :integer &&
|
|
30
|
-
reflections.find { |_, r| r.belongs_to? && r.foreign_key.to_s == attr_name }
|
|
32
|
+
reflections.find { |_, r| r.belongs_to? && r.foreign_key.to_s == attr_name }&.last
|
|
31
33
|
rescue ::ActiveRecord::StatementInvalid
|
|
32
34
|
# this is for when models are referenced in initializers before migrations have been run
|
|
33
35
|
raise if connection.open_transactions > 0
|
|
@@ -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,23 +56,28 @@ 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]
|
|
67
78
|
# a polymorphic association has to be discovered at runtime. This code ends up being something like
|
|
68
|
-
# context_type
|
|
69
|
-
"read_attribute(:#{reflection.foreign_type})
|
|
79
|
+
# context_type.&.constantize&.shard_category || :primary
|
|
80
|
+
"read_attribute(:#{reflection.foreign_type})&.constantize&.shard_category || :primary"
|
|
70
81
|
else
|
|
71
82
|
# otherwise we can just return a symbol for the statically known type of the association
|
|
72
83
|
reflection.klass.shard_category.inspect
|
|
@@ -79,22 +90,43 @@ module Switchman
|
|
|
79
90
|
def define_method_original_attribute(attr_name)
|
|
80
91
|
if sharded_column?(attr_name)
|
|
81
92
|
reflection = reflection_for_integer_attribute(attr_name)
|
|
82
|
-
|
|
93
|
+
if attr_name == "id" && ::Rails.version >= '5.1.2'
|
|
94
|
+
return if self.method_defined?(:original_id)
|
|
95
|
+
owner = self
|
|
96
|
+
else
|
|
97
|
+
owner = generated_attribute_methods
|
|
98
|
+
end
|
|
99
|
+
owner.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
83
100
|
# rename the original method to original_
|
|
84
101
|
alias_method 'original_#{attr_name}', '#{attr_name}'
|
|
85
102
|
# and replace with one that transposes the id
|
|
86
|
-
def
|
|
87
|
-
|
|
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)
|
|
88
120
|
end
|
|
89
|
-
alias_method '#{attr_name}', :
|
|
90
|
-
undef_method :
|
|
121
|
+
alias_method '#{attr_name}', :__temp_relative_attribute__
|
|
122
|
+
undef_method :__temp_relative_attribute__
|
|
91
123
|
|
|
92
124
|
alias_method 'original_#{attr_name}=', '#{attr_name}='
|
|
93
|
-
def
|
|
125
|
+
def __temp_relative_attribute_assignment__(new_value)
|
|
94
126
|
self.original_#{attr_name} = Shard.relative_id_for(new_value, Shard.current(#{shard_category_code_for_reflection(reflection)}), shard)
|
|
95
127
|
end
|
|
96
|
-
alias_method '#{attr_name}=', :
|
|
97
|
-
undef_method :
|
|
128
|
+
alias_method '#{attr_name}=', :__temp_relative_attribute_assignment__
|
|
129
|
+
undef_method :__temp_relative_attribute_assignment__
|
|
98
130
|
RUBY
|
|
99
131
|
else
|
|
100
132
|
define_method_unsharded_column(attr_name, 'global')
|
|
@@ -104,11 +136,11 @@ module Switchman
|
|
|
104
136
|
def define_method_unsharded_column(attr_name, prefix)
|
|
105
137
|
return if columns_hash["#{prefix}_#{attr_name}"]
|
|
106
138
|
generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
107
|
-
def
|
|
139
|
+
def __temp_unsharded_attribute__
|
|
108
140
|
raise NoMethodError, "undefined method `#{prefix}_#{attr_name}'; are you missing an association?"
|
|
109
141
|
end
|
|
110
|
-
alias_method '#{prefix}_#{attr_name}', :
|
|
111
|
-
undef_method :
|
|
142
|
+
alias_method '#{prefix}_#{attr_name}', :__temp_unsharded_attribute__
|
|
143
|
+
undef_method :__temp_unsharded_attribute__
|
|
112
144
|
RUBY
|
|
113
145
|
end
|
|
114
146
|
end
|
|
@@ -117,7 +149,7 @@ module Switchman
|
|
|
117
149
|
klass.extend(ClassMethods)
|
|
118
150
|
klass.attribute_method_prefix "global_", "local_", "original_"
|
|
119
151
|
end
|
|
120
|
-
|
|
152
|
+
|
|
121
153
|
# ensure that we're using the sharded attribute method
|
|
122
154
|
# and not the silly one in AR::AttributeMethods::PrimaryKey
|
|
123
155
|
def id
|
|
@@ -1,11 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Switchman
|
|
2
4
|
module ActiveRecord
|
|
3
5
|
module Base
|
|
4
6
|
module ClassMethods
|
|
5
7
|
delegate :shard, to: :all
|
|
6
8
|
|
|
9
|
+
def find_ids_in_ranges(opts={}, &block)
|
|
10
|
+
opts.reverse_merge!(:loose => true)
|
|
11
|
+
all.find_ids_in_ranges(opts, &block)
|
|
12
|
+
end
|
|
13
|
+
|
|
7
14
|
def shard_category
|
|
8
|
-
|
|
15
|
+
connection_specification_name.to_sym
|
|
9
16
|
end
|
|
10
17
|
|
|
11
18
|
def shard_category=(category)
|
|
@@ -14,34 +21,32 @@ module Switchman
|
|
|
14
21
|
categories[shard_category].delete(self)
|
|
15
22
|
categories.delete(shard_category) if categories[shard_category].empty?
|
|
16
23
|
end
|
|
17
|
-
connection_handler.uninitialize_ar(self)
|
|
18
24
|
categories[category] ||= []
|
|
19
25
|
categories[category] << self
|
|
20
|
-
|
|
21
|
-
connection_handler.initialize_categories(superclass)
|
|
26
|
+
self.connection_specification_name = category.to_s
|
|
22
27
|
end
|
|
23
28
|
|
|
24
29
|
def integral_id?
|
|
25
30
|
if @integral_id == nil
|
|
26
|
-
@integral_id = columns_hash[primary_key]
|
|
31
|
+
@integral_id = columns_hash[primary_key]&.type == :integer
|
|
27
32
|
end
|
|
28
33
|
@integral_id
|
|
29
34
|
end
|
|
30
35
|
|
|
31
|
-
def transaction(
|
|
36
|
+
def transaction(**)
|
|
32
37
|
if self != ::ActiveRecord::Base && current_scope
|
|
33
38
|
current_scope.activate do
|
|
34
39
|
db = Shard.current(shard_category).database_server
|
|
35
|
-
if ::
|
|
36
|
-
db.
|
|
40
|
+
if ::GuardRail.environment != db.guard_rail_environment
|
|
41
|
+
db.unguard { super }
|
|
37
42
|
else
|
|
38
43
|
super
|
|
39
44
|
end
|
|
40
45
|
end
|
|
41
46
|
else
|
|
42
47
|
db = Shard.current(shard_category).database_server
|
|
43
|
-
if ::
|
|
44
|
-
db.
|
|
48
|
+
if ::GuardRail.environment != db.guard_rail_environment
|
|
49
|
+
db.unguard { super }
|
|
45
50
|
else
|
|
46
51
|
super
|
|
47
52
|
end
|
|
@@ -65,21 +70,29 @@ module Switchman
|
|
|
65
70
|
result
|
|
66
71
|
end
|
|
67
72
|
end
|
|
68
|
-
end
|
|
69
73
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if self.class.sharded_primary_key?
|
|
75
|
-
@shard = Shard.shard_for(self[self.class.primary_key], Shard.current(self.class.shard_category))
|
|
76
|
-
else
|
|
77
|
-
@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?
|
|
78
78
|
end
|
|
79
79
|
end
|
|
80
80
|
end
|
|
81
81
|
end
|
|
82
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
|
+
|
|
83
96
|
def shard
|
|
84
97
|
@shard || Shard.current(self.class.shard_category) || Shard.default
|
|
85
98
|
end
|
|
@@ -94,22 +107,18 @@ module Switchman
|
|
|
94
107
|
end
|
|
95
108
|
end
|
|
96
109
|
|
|
97
|
-
def
|
|
98
|
-
self.class.base_class
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
def save(*args)
|
|
110
|
+
def save(*, **)
|
|
102
111
|
@shard_set_in_stone = true
|
|
103
|
-
|
|
112
|
+
(self.class.current_scope || self.class.default_scoped).shard(shard, :implicit).scoping { super }
|
|
104
113
|
end
|
|
105
114
|
|
|
106
|
-
def save!(
|
|
115
|
+
def save!(*, **)
|
|
107
116
|
@shard_set_in_stone = true
|
|
108
|
-
|
|
117
|
+
(self.class.current_scope || self.class.default_scoped).shard(shard, :implicit).scoping { super }
|
|
109
118
|
end
|
|
110
119
|
|
|
111
120
|
def destroy
|
|
112
|
-
|
|
121
|
+
self.class.shard(shard, :implicit).scoping { super }
|
|
113
122
|
end
|
|
114
123
|
|
|
115
124
|
def clone
|
|
@@ -121,14 +130,14 @@ module Switchman
|
|
|
121
130
|
result
|
|
122
131
|
end
|
|
123
132
|
|
|
124
|
-
def transaction(
|
|
133
|
+
def transaction(**kwargs, &block)
|
|
125
134
|
shard.activate(self.class.shard_category) do
|
|
126
|
-
self.class.transaction(
|
|
135
|
+
self.class.transaction(**kwargs, &block)
|
|
127
136
|
end
|
|
128
137
|
end
|
|
129
138
|
|
|
130
139
|
def hash
|
|
131
|
-
self.class.sharded_primary_key? ? Shard.global_id_for(id).hash : super
|
|
140
|
+
self.class.sharded_primary_key? ? self.class.hash ^ Shard.global_id_for(id).hash : super
|
|
132
141
|
end
|
|
133
142
|
|
|
134
143
|
def to_param
|
|
@@ -141,6 +150,42 @@ module Switchman
|
|
|
141
150
|
@shard_set_in_stone = false
|
|
142
151
|
copy
|
|
143
152
|
end
|
|
153
|
+
|
|
154
|
+
def quoted_id
|
|
155
|
+
return super unless self.class.sharded_primary_key?
|
|
156
|
+
# do this the Rails 4.2 way, so that if Shard.current != self.shard, the id gets transposed
|
|
157
|
+
self.class.connection.quote(id)
|
|
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
|
|
144
189
|
end
|
|
145
190
|
end
|
|
146
191
|
end
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Switchman
|
|
2
4
|
module ActiveRecord
|
|
3
5
|
module Batches
|
|
4
6
|
def batch_order
|
|
5
|
-
"#{connection.quote_local_table_name(table_name)}.#{quoted_primary_key} ASC"
|
|
7
|
+
::Arel.sql("#{connection.quote_local_table_name(table_name)}.#{quoted_primary_key} ASC")
|
|
6
8
|
end
|
|
7
9
|
end
|
|
8
10
|
end
|