switchman 2.0.2 → 3.0.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 +10 -2
- data/app/models/switchman/shard.rb +234 -270
- data/app/models/switchman/unsharded_record.rb +7 -0
- data/db/migrate/20130328212039_create_switchman_shards.rb +1 -1
- data/db/migrate/20130328224244_create_default_shard.rb +5 -5
- data/db/migrate/20161206323434_add_back_default_string_limits_switchman.rb +1 -0
- data/db/migrate/20180828183945_add_default_shard_index.rb +1 -1
- data/db/migrate/20180828192111_add_timestamps_to_shards.rb +7 -5
- data/db/migrate/20190114212900_add_unique_name_indexes.rb +5 -3
- data/lib/switchman.rb +3 -3
- data/lib/switchman/action_controller/caching.rb +2 -2
- data/lib/switchman/active_record/abstract_adapter.rb +1 -0
- data/lib/switchman/active_record/association.rb +78 -85
- data/lib/switchman/active_record/attribute_methods.rb +58 -52
- data/lib/switchman/active_record/base.rb +58 -59
- data/lib/switchman/active_record/calculations.rb +73 -66
- data/lib/switchman/active_record/connection_pool.rb +14 -41
- data/lib/switchman/active_record/database_configurations.rb +34 -0
- data/lib/switchman/active_record/database_configurations/database_config.rb +13 -0
- data/lib/switchman/active_record/finder_methods.rb +11 -16
- data/lib/switchman/active_record/log_subscriber.rb +4 -8
- data/lib/switchman/active_record/migration.rb +6 -15
- data/lib/switchman/active_record/model_schema.rb +1 -1
- data/lib/switchman/active_record/persistence.rb +4 -6
- data/lib/switchman/active_record/postgresql_adapter.rb +42 -53
- data/lib/switchman/active_record/predicate_builder.rb +1 -1
- data/lib/switchman/active_record/query_cache.rb +18 -19
- data/lib/switchman/active_record/query_methods.rb +172 -181
- data/lib/switchman/active_record/reflection.rb +6 -10
- data/lib/switchman/active_record/relation.rb +27 -21
- data/lib/switchman/active_record/spawn_methods.rb +27 -29
- data/lib/switchman/active_record/statement_cache.rb +18 -35
- data/lib/switchman/active_record/tasks/database_tasks.rb +16 -0
- data/lib/switchman/active_support/cache.rb +3 -5
- data/lib/switchman/arel.rb +13 -8
- data/lib/switchman/database_server.rb +121 -142
- data/lib/switchman/default_shard.rb +52 -16
- data/lib/switchman/engine.rb +61 -57
- data/lib/switchman/environment.rb +4 -8
- data/lib/switchman/errors.rb +1 -0
- data/lib/switchman/guard_rail.rb +6 -19
- data/lib/switchman/guard_rail/relation.rb +5 -7
- data/lib/switchman/r_spec_helper.rb +29 -37
- data/lib/switchman/rails.rb +14 -12
- data/lib/switchman/schema_cache.rb +1 -1
- data/lib/switchman/sharded_instrumenter.rb +1 -1
- data/lib/switchman/standard_error.rb +15 -3
- data/lib/switchman/test_helper.rb +6 -10
- data/lib/switchman/version.rb +1 -1
- data/lib/tasks/switchman.rake +54 -69
- metadata +100 -44
- data/lib/switchman/active_record/batches.rb +0 -11
- data/lib/switchman/active_record/connection_handler.rb +0 -172
- data/lib/switchman/active_record/where_clause_factory.rb +0 -36
- data/lib/switchman/connection_pool_proxy.rb +0 -169
|
@@ -5,7 +5,7 @@ class CreateSwitchmanShards < ActiveRecord::Migration[4.2]
|
|
|
5
5
|
create_table :switchman_shards do |t|
|
|
6
6
|
t.string :name
|
|
7
7
|
t.string :database_server_id
|
|
8
|
-
t.boolean :default, :
|
|
8
|
+
t.boolean :default, default: false, null: false
|
|
9
9
|
end
|
|
10
10
|
end
|
|
11
11
|
end
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
class CreateDefaultShard < ActiveRecord::Migration[4.2]
|
|
4
4
|
def up
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
return if Switchman::Shard.default.is_a?(Switchman::Shard)
|
|
6
|
+
|
|
7
|
+
Switchman::Shard.reset_column_information
|
|
8
|
+
Switchman::Shard.create!(default: true)
|
|
9
|
+
Switchman::Shard.default(reload: true)
|
|
10
10
|
end
|
|
11
11
|
end
|
|
@@ -6,7 +6,7 @@ class AddDefaultShardIndex < ActiveRecord::Migration[4.2]
|
|
|
6
6
|
change_column_default :switchman_shards, :default, false
|
|
7
7
|
change_column_null :switchman_shards, :default, false
|
|
8
8
|
options = if connection.adapter_name == 'PostgreSQL'
|
|
9
|
-
{ unique: true, where: "
|
|
9
|
+
{ unique: true, where: '"default"' }
|
|
10
10
|
else
|
|
11
11
|
{}
|
|
12
12
|
end
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
class AddTimestampsToShards < ActiveRecord::Migration[4.2]
|
|
4
|
+
disable_ddl_transaction!
|
|
5
|
+
|
|
4
6
|
def change
|
|
5
7
|
add_timestamps :switchman_shards, null: true
|
|
6
8
|
now = Time.now.utc
|
|
@@ -8,10 +10,10 @@ class AddTimestampsToShards < ActiveRecord::Migration[4.2]
|
|
|
8
10
|
change_column_null :switchman_shards, :updated_at, false
|
|
9
11
|
change_column_null :switchman_shards, :created_at, false
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
return unless Switchman::Shard.current.default?
|
|
14
|
+
|
|
15
|
+
Switchman::Shard.connection.schema_cache.clear!
|
|
16
|
+
Switchman::Shard.reset_column_information
|
|
17
|
+
Switchman::Shard.columns
|
|
16
18
|
end
|
|
17
19
|
end
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
class AddUniqueNameIndexes < ActiveRecord::Migration[4.2]
|
|
4
4
|
def change
|
|
5
|
-
add_index :switchman_shards, [
|
|
6
|
-
add_index :switchman_shards, :database_server_id, unique: true, where:
|
|
7
|
-
|
|
5
|
+
add_index :switchman_shards, %i[database_server_id name], unique: true
|
|
6
|
+
add_index :switchman_shards, :database_server_id, unique: true, where: 'name IS NULL',
|
|
7
|
+
name: 'index_switchman_shards_unique_primary_shard'
|
|
8
|
+
add_index :switchman_shards, '(true)', unique: true, where: 'database_server_id IS NULL AND name IS NULL',
|
|
9
|
+
name: 'index_switchman_shards_unique_primary_db_and_shard'
|
|
8
10
|
end
|
|
9
11
|
end
|
data/lib/switchman.rb
CHANGED
|
@@ -13,7 +13,7 @@ module Switchman
|
|
|
13
13
|
# disallow assigning to ActionController::Base.cache_store or
|
|
14
14
|
# ActionController::Base#cache_store for the same reasons we disallow
|
|
15
15
|
# assigning to Rails.cache
|
|
16
|
-
def cache_store=(
|
|
16
|
+
def cache_store=(_cache)
|
|
17
17
|
raise NoMethodError
|
|
18
18
|
end
|
|
19
19
|
end
|
|
@@ -21,7 +21,7 @@ module Switchman
|
|
|
21
21
|
include ConfigMethods
|
|
22
22
|
|
|
23
23
|
def self.included(base)
|
|
24
|
-
base.
|
|
24
|
+
base.singleton_class.prepend(ConfigMethods)
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
27
|
end
|
|
@@ -8,27 +8,17 @@ module Switchman
|
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
def build_record(*args)
|
|
11
|
-
|
|
11
|
+
shard.activate { super }
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def load_target
|
|
15
|
-
|
|
15
|
+
shard.activate { super }
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def scope
|
|
19
|
-
shard_value = @reflection.options[:multishard] ? @owner :
|
|
19
|
+
shard_value = @reflection.options[:multishard] ? @owner : shard
|
|
20
20
|
@owner.shard.activate { super.shard(shard_value, :association) }
|
|
21
21
|
end
|
|
22
|
-
|
|
23
|
-
def creation_attributes
|
|
24
|
-
attributes = super
|
|
25
|
-
|
|
26
|
-
# translate keys
|
|
27
|
-
if reflection.macro.in?([:has_one, :has_many]) && !options[:through]
|
|
28
|
-
attributes[reflection.foreign_key] = Shard.relative_id_for(owner[reflection.active_record_primary_key], owner.shard, self.shard)
|
|
29
|
-
end
|
|
30
|
-
attributes
|
|
31
|
-
end
|
|
32
22
|
end
|
|
33
23
|
|
|
34
24
|
module CollectionAssociation
|
|
@@ -36,15 +26,19 @@ module Switchman
|
|
|
36
26
|
shards = reflection.options[:multishard] && owner.respond_to?(:associated_shards) ? owner.associated_shards : [shard]
|
|
37
27
|
# activate both the owner and the target's shard category, so that Reflection#join_id_for,
|
|
38
28
|
# when called for the owner, will be returned relative to shard the query will execute on
|
|
39
|
-
Shard.with_each_shard(shards, [klass.
|
|
29
|
+
Shard.with_each_shard(shards, [klass.connection_classes, owner.class.connection_classes].uniq) do
|
|
40
30
|
super
|
|
41
31
|
end
|
|
42
32
|
end
|
|
33
|
+
|
|
34
|
+
def _create_record(*)
|
|
35
|
+
shard.activate { super }
|
|
36
|
+
end
|
|
43
37
|
end
|
|
44
38
|
|
|
45
39
|
module BelongsToAssociation
|
|
46
|
-
def replace_keys(record)
|
|
47
|
-
if record
|
|
40
|
+
def replace_keys(record, force: false)
|
|
41
|
+
if record&.class&.sharded_column?(reflection.association_primary_key(record.class))
|
|
48
42
|
foreign_id = record[reflection.association_primary_key(record.class)]
|
|
49
43
|
owner[reflection.foreign_key] = Shard.relative_id_for(foreign_id, record.shard, owner.shard)
|
|
50
44
|
else
|
|
@@ -54,7 +48,7 @@ module Switchman
|
|
|
54
48
|
|
|
55
49
|
def shard
|
|
56
50
|
if @owner.class.sharded_column?(@reflection.foreign_key) &&
|
|
57
|
-
|
|
51
|
+
(foreign_id = @owner[@reflection.foreign_key])
|
|
58
52
|
Shard.shard_for(foreign_id, @owner.shard)
|
|
59
53
|
else
|
|
60
54
|
super
|
|
@@ -62,9 +56,22 @@ module Switchman
|
|
|
62
56
|
end
|
|
63
57
|
end
|
|
64
58
|
|
|
65
|
-
module
|
|
66
|
-
|
|
59
|
+
module ForeignAssociation
|
|
60
|
+
# significant change:
|
|
61
|
+
# * transpose the key to the correct shard
|
|
62
|
+
def set_owner_attributes(record) # rubocop:disable Naming/AccessorMethodName
|
|
63
|
+
return if options[:through]
|
|
64
|
+
|
|
65
|
+
key = owner._read_attribute(reflection.join_foreign_key)
|
|
66
|
+
key = Shard.relative_id_for(key, owner.shard, shard)
|
|
67
|
+
record._write_attribute(reflection.join_primary_key, key)
|
|
68
|
+
|
|
69
|
+
record._write_attribute(reflection.type, owner.class.polymorphic_name) if reflection.type
|
|
67
70
|
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
module Extension
|
|
74
|
+
def self.build(_model, _reflection); end
|
|
68
75
|
|
|
69
76
|
def self.valid_options
|
|
70
77
|
[:multishard]
|
|
@@ -75,44 +82,35 @@ module Switchman
|
|
|
75
82
|
|
|
76
83
|
module Preloader
|
|
77
84
|
module Association
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
85
|
+
# Copypasta from Activerecord but with added global_id_for goodness.
|
|
86
|
+
def records_for(ids)
|
|
87
|
+
scope.where(association_key_name => ids).load do |record|
|
|
88
|
+
global_key = if record.class.connection_classes == UnshardedRecord
|
|
89
|
+
convert_key(record[association_key_name])
|
|
90
|
+
else
|
|
91
|
+
Shard.global_id_for(record[association_key_name], record.shard)
|
|
92
|
+
end
|
|
93
|
+
owner = owners_by_key[convert_key(global_key)].first
|
|
94
|
+
association = owner.association(reflection.name)
|
|
95
|
+
association.set_inverse_instance(record)
|
|
83
96
|
end
|
|
84
97
|
end
|
|
85
98
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
106
|
-
def associated_records_by_owner(preloader = nil)
|
|
107
|
-
owners_map = owners_by_key
|
|
99
|
+
# significant changes:
|
|
100
|
+
# * partition_by_shard the records_for call
|
|
101
|
+
# * re-globalize the fetched owner id before looking up in the map
|
|
102
|
+
def load_records
|
|
103
|
+
# owners can be duplicated when a relation has a collection association join
|
|
104
|
+
# #compare_by_identity makes such owners different hash keys
|
|
105
|
+
@records_by_owner = {}.compare_by_identity
|
|
108
106
|
|
|
109
|
-
if
|
|
110
|
-
|
|
107
|
+
if owner_keys.empty?
|
|
108
|
+
raw_records = []
|
|
111
109
|
else
|
|
112
110
|
# determine the shard to search for each owner
|
|
113
111
|
if reflection.macro == :belongs_to
|
|
114
112
|
# for belongs_to, it's the shard of the foreign_key
|
|
115
|
-
partition_proc =
|
|
113
|
+
partition_proc = lambda do |owner|
|
|
116
114
|
if owner.class.sharded_column?(owner_key_name)
|
|
117
115
|
Shard.shard_for(owner[owner_key_name], owner.shard)
|
|
118
116
|
else
|
|
@@ -122,61 +120,56 @@ module Switchman
|
|
|
122
120
|
elsif !reflection.options[:multishard]
|
|
123
121
|
# for non-multishard associations, it's *just* the owner's shard
|
|
124
122
|
partition_proc = ->(owner) { owner.shard }
|
|
125
|
-
else
|
|
126
|
-
# for multishard associations, it's the owner object itself
|
|
127
|
-
# (all associated shards)
|
|
128
|
-
|
|
129
|
-
# this is the default behavior of partition_by_shard, so just let it be nil
|
|
130
|
-
# to avoid the proc call
|
|
131
|
-
# partition_proc = ->(owner) { owner }
|
|
132
123
|
end
|
|
133
124
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
key = owner[owner_key_name]
|
|
141
|
-
if key && owner.class.sharded_column?(owner_key_name)
|
|
142
|
-
key = Shard.relative_id_for(key, owner.shard, Shard.current(owner.class.shard_category))
|
|
143
|
-
end
|
|
144
|
-
key && key.to_s
|
|
125
|
+
raw_records = Shard.partition_by_shard(owners, partition_proc) do |partitioned_owners|
|
|
126
|
+
relative_owner_keys = partitioned_owners.map do |owner|
|
|
127
|
+
key = owner[owner_key_name]
|
|
128
|
+
if key && owner.class.sharded_column?(owner_key_name)
|
|
129
|
+
key = Shard.relative_id_for(key, owner.shard,
|
|
130
|
+
Shard.current(klass.connection_classes))
|
|
145
131
|
end
|
|
146
|
-
|
|
147
|
-
relative_owner_keys.uniq!
|
|
148
|
-
records_for(relative_owner_keys)
|
|
132
|
+
convert_key(key)
|
|
149
133
|
end
|
|
134
|
+
relative_owner_keys.compact!
|
|
135
|
+
relative_owner_keys.uniq!
|
|
136
|
+
records_for(relative_owner_keys)
|
|
150
137
|
end
|
|
151
|
-
records.flatten!
|
|
152
138
|
end
|
|
153
139
|
|
|
154
|
-
@preloaded_records =
|
|
140
|
+
@preloaded_records = raw_records.select do |record|
|
|
141
|
+
assignments = false
|
|
155
142
|
|
|
156
|
-
# Each record may have multiple owners, and vice-versa
|
|
157
|
-
records_by_owner = owners.each_with_object({}) do |owner,h|
|
|
158
|
-
h[owner] = []
|
|
159
|
-
end
|
|
160
|
-
records.each do |record|
|
|
161
143
|
owner_key = record[association_key_name]
|
|
162
|
-
|
|
144
|
+
if owner_key && record.class.sharded_column?(association_key_name)
|
|
145
|
+
owner_key = Shard.global_id_for(owner_key,
|
|
146
|
+
record.shard)
|
|
147
|
+
end
|
|
163
148
|
|
|
164
|
-
|
|
165
|
-
owner
|
|
166
|
-
|
|
149
|
+
owners_by_key[convert_key(owner_key)].each do |owner|
|
|
150
|
+
entries = (@records_by_owner[owner] ||= [])
|
|
151
|
+
|
|
152
|
+
if reflection.collection? || entries.empty?
|
|
153
|
+
entries << record
|
|
154
|
+
assignments = true
|
|
155
|
+
end
|
|
167
156
|
end
|
|
157
|
+
|
|
158
|
+
assignments
|
|
168
159
|
end
|
|
169
|
-
records_by_owner
|
|
170
160
|
end
|
|
171
161
|
|
|
162
|
+
# significant change: globalize keys on sharded columns
|
|
172
163
|
def owners_by_key
|
|
173
|
-
@owners_by_key ||= owners.
|
|
164
|
+
@owners_by_key ||= owners.each_with_object({}) do |owner, result|
|
|
174
165
|
key = owner[owner_key_name]
|
|
175
166
|
key = Shard.global_id_for(key, owner.shard) if key && owner.class.sharded_column?(owner_key_name)
|
|
176
|
-
key
|
|
167
|
+
key = convert_key(key)
|
|
168
|
+
(result[key] ||= []) << owner if key
|
|
177
169
|
end
|
|
178
170
|
end
|
|
179
171
|
|
|
172
|
+
# significant change: don't cache scope (since it could be for different shards)
|
|
180
173
|
def scope
|
|
181
174
|
build_scope
|
|
182
175
|
end
|
|
@@ -206,7 +199,7 @@ module Switchman
|
|
|
206
199
|
# this seems counter-intuitive, but the autosave code will assign to attribute bypassing switchman,
|
|
207
200
|
# after reading the id attribute _without_ bypassing switchman. So we need Shard.current for the
|
|
208
201
|
# category of the associated record to match Shard.current for the category of self
|
|
209
|
-
shard.activate(
|
|
202
|
+
shard.activate(connection_classes_for_reflection(reflection)) { super }
|
|
210
203
|
end
|
|
211
204
|
end
|
|
212
205
|
end
|
|
@@ -4,22 +4,23 @@ module Switchman
|
|
|
4
4
|
module ActiveRecord
|
|
5
5
|
module AttributeMethods
|
|
6
6
|
module ClassMethods
|
|
7
|
-
|
|
8
7
|
def sharded_primary_key?
|
|
9
|
-
self
|
|
8
|
+
!(self <= UnshardedRecord) && integral_id?
|
|
10
9
|
end
|
|
11
10
|
|
|
12
11
|
def sharded_foreign_key?(column_name)
|
|
13
12
|
reflection = reflection_for_integer_attribute(column_name.to_s)
|
|
14
13
|
return false unless reflection
|
|
14
|
+
|
|
15
15
|
reflection.options[:polymorphic] || reflection.klass.sharded_primary_key?
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def sharded_column?(column_name)
|
|
19
19
|
column_name = column_name.to_s
|
|
20
20
|
@sharded_column_values ||= {}
|
|
21
|
-
unless @sharded_column_values.
|
|
22
|
-
@sharded_column_values[column_name] =
|
|
21
|
+
unless @sharded_column_values.key?(column_name)
|
|
22
|
+
@sharded_column_values[column_name] =
|
|
23
|
+
(column_name == primary_key && sharded_primary_key?) || sharded_foreign_key?(column_name)
|
|
23
24
|
end
|
|
24
25
|
@sharded_column_values[column_name]
|
|
25
26
|
end
|
|
@@ -29,109 +30,114 @@ module Switchman
|
|
|
29
30
|
def reflection_for_integer_attribute(attr_name)
|
|
30
31
|
attr_name = attr_name.to_s
|
|
31
32
|
columns_hash[attr_name] && columns_hash[attr_name].type == :integer &&
|
|
32
|
-
|
|
33
|
+
reflections.find { |_, r| r.belongs_to? && r.foreign_key.to_s == attr_name }&.last
|
|
33
34
|
rescue ::ActiveRecord::StatementInvalid
|
|
34
35
|
# this is for when models are referenced in initializers before migrations have been run
|
|
35
|
-
raise if connection.open_transactions
|
|
36
|
+
raise if connection.open_transactions.positive?
|
|
36
37
|
end
|
|
37
38
|
|
|
38
|
-
def define_method_global_attribute(attr_name)
|
|
39
|
+
def define_method_global_attribute(attr_name, owner:)
|
|
39
40
|
if sharded_column?(attr_name)
|
|
40
|
-
|
|
41
|
-
def
|
|
42
|
-
Shard.global_id_for(original_#{attr_name}, shard)
|
|
41
|
+
owner << <<-RUBY
|
|
42
|
+
def global_#{attr_name}
|
|
43
|
+
::Switchman::Shard.global_id_for(original_#{attr_name}, shard)
|
|
43
44
|
end
|
|
44
|
-
alias_method 'global_#{attr_name}', :__temp__
|
|
45
|
-
undef_method :__temp__
|
|
46
45
|
RUBY
|
|
47
46
|
else
|
|
48
|
-
define_method_unsharded_column(attr_name, 'global')
|
|
47
|
+
define_method_unsharded_column(attr_name, 'global', owner)
|
|
49
48
|
end
|
|
50
49
|
end
|
|
51
50
|
|
|
52
|
-
def define_method_local_attribute(attr_name)
|
|
51
|
+
def define_method_local_attribute(attr_name, owner:)
|
|
53
52
|
if sharded_column?(attr_name)
|
|
54
|
-
|
|
55
|
-
def
|
|
56
|
-
Shard.local_id_for(original_#{attr_name}).first
|
|
53
|
+
owner << <<-RUBY
|
|
54
|
+
def local_#{attr_name}
|
|
55
|
+
::Switchman::Shard.local_id_for(original_#{attr_name}).first
|
|
57
56
|
end
|
|
58
|
-
alias_method 'local_#{attr_name}', :__temp__
|
|
59
|
-
undef_method :__temp__
|
|
60
57
|
RUBY
|
|
61
58
|
else
|
|
62
|
-
define_method_unsharded_column(attr_name, 'local')
|
|
59
|
+
define_method_unsharded_column(attr_name, 'local', owner)
|
|
63
60
|
end
|
|
64
61
|
end
|
|
65
62
|
|
|
66
|
-
# see also Base#
|
|
63
|
+
# see also Base#connection_classes_for_reflection
|
|
67
64
|
# the difference being this will output static strings for the common cases, making them
|
|
68
65
|
# more performant
|
|
69
|
-
def
|
|
66
|
+
def connection_classes_code_for_reflection(reflection)
|
|
70
67
|
if reflection
|
|
71
68
|
if reflection.options[:polymorphic]
|
|
72
69
|
# a polymorphic association has to be discovered at runtime. This code ends up being something like
|
|
73
|
-
# context_type.&.constantize&.
|
|
74
|
-
"read_attribute(:#{reflection.foreign_type})&.constantize&.
|
|
70
|
+
# context_type.&.constantize&.connection_classes
|
|
71
|
+
"read_attribute(:#{reflection.foreign_type})&.constantize&.connection_classes"
|
|
75
72
|
else
|
|
76
73
|
# otherwise we can just return a symbol for the statically known type of the association
|
|
77
|
-
reflection.klass.
|
|
74
|
+
"::#{reflection.klass.connection_classes.name}"
|
|
78
75
|
end
|
|
79
76
|
else
|
|
80
|
-
|
|
77
|
+
"::#{connection_classes.name}"
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# just a dummy class with the proper interface that calls module_eval immediately
|
|
82
|
+
class CodeGenerator
|
|
83
|
+
def initialize(mod, line)
|
|
84
|
+
@module = mod
|
|
85
|
+
@line = line
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def <<(string)
|
|
89
|
+
@module.module_eval(string, __FILE__, @line)
|
|
81
90
|
end
|
|
82
91
|
end
|
|
83
92
|
|
|
84
|
-
def define_method_original_attribute(attr_name)
|
|
93
|
+
def define_method_original_attribute(attr_name, owner:)
|
|
85
94
|
if sharded_column?(attr_name)
|
|
86
95
|
reflection = reflection_for_integer_attribute(attr_name)
|
|
87
|
-
if attr_name ==
|
|
88
|
-
return if
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
owner = generated_attribute_methods
|
|
96
|
+
if attr_name == 'id'
|
|
97
|
+
return if method_defined?(:original_id)
|
|
98
|
+
|
|
99
|
+
owner = CodeGenerator.new(self, __LINE__ + 4)
|
|
92
100
|
end
|
|
93
|
-
|
|
94
|
-
|
|
101
|
+
|
|
102
|
+
owner << <<-RUBY
|
|
103
|
+
# rename the original method to original_*
|
|
95
104
|
alias_method 'original_#{attr_name}', '#{attr_name}'
|
|
96
105
|
# and replace with one that transposes the id
|
|
97
|
-
def
|
|
98
|
-
Shard.relative_id_for(original_#{attr_name}, shard, Shard.current(#{
|
|
106
|
+
def #{attr_name}
|
|
107
|
+
::Switchman::Shard.relative_id_for(original_#{attr_name}, shard, ::Switchman::Shard.current(#{connection_classes_code_for_reflection(reflection)}))
|
|
99
108
|
end
|
|
100
|
-
alias_method '#{attr_name}', :__temp__
|
|
101
|
-
undef_method :__temp__
|
|
102
109
|
|
|
103
110
|
alias_method 'original_#{attr_name}=', '#{attr_name}='
|
|
104
|
-
def
|
|
105
|
-
self.original_#{attr_name} = Shard.relative_id_for(new_value, Shard.current(#{
|
|
111
|
+
def #{attr_name}=(new_value)
|
|
112
|
+
self.original_#{attr_name} = ::Switchman::Shard.relative_id_for(new_value, ::Switchman::Shard.current(#{connection_classes_code_for_reflection(reflection)}), shard)
|
|
106
113
|
end
|
|
107
|
-
alias_method '#{attr_name}=', :__temp__
|
|
108
|
-
undef_method :__temp__
|
|
109
114
|
RUBY
|
|
110
115
|
else
|
|
111
|
-
define_method_unsharded_column(attr_name, 'global')
|
|
116
|
+
define_method_unsharded_column(attr_name, 'global', owner)
|
|
112
117
|
end
|
|
113
118
|
end
|
|
114
119
|
|
|
115
|
-
def define_method_unsharded_column(attr_name, prefix)
|
|
120
|
+
def define_method_unsharded_column(attr_name, prefix, owner)
|
|
116
121
|
return if columns_hash["#{prefix}_#{attr_name}"]
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
undef_method :__temp__
|
|
122
|
+
|
|
123
|
+
owner << <<-RUBY
|
|
124
|
+
def #{prefix}_#{attr_name}
|
|
125
|
+
raise NoMethodError, "undefined method `#{prefix}_#{attr_name}'; are you missing an association?"
|
|
126
|
+
end
|
|
123
127
|
RUBY
|
|
124
128
|
end
|
|
125
129
|
end
|
|
126
130
|
|
|
127
131
|
def self.included(klass)
|
|
128
|
-
klass.
|
|
129
|
-
klass.attribute_method_prefix
|
|
132
|
+
klass.singleton_class.include(ClassMethods)
|
|
133
|
+
klass.attribute_method_prefix 'global_', 'local_', 'original_'
|
|
130
134
|
end
|
|
131
135
|
|
|
132
136
|
# ensure that we're using the sharded attribute method
|
|
133
137
|
# and not the silly one in AR::AttributeMethods::PrimaryKey
|
|
134
138
|
def id
|
|
139
|
+
return super if is_a?(Shard)
|
|
140
|
+
|
|
135
141
|
self.class.define_attribute_methods
|
|
136
142
|
super
|
|
137
143
|
end
|