switchman 3.0.1 → 4.2.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 +4 -4
- data/Rakefile +16 -15
- data/db/migrate/20180828183945_add_default_shard_index.rb +2 -2
- data/db/migrate/20180828192111_add_timestamps_to_shards.rb +1 -1
- data/db/migrate/20190114212900_add_unique_name_indexes.rb +10 -4
- data/lib/switchman/action_controller/caching.rb +2 -2
- data/lib/switchman/active_record/abstract_adapter.rb +11 -18
- data/lib/switchman/active_record/associations.rb +315 -0
- data/lib/switchman/active_record/attribute_methods.rb +191 -79
- data/lib/switchman/active_record/base.rb +204 -50
- data/lib/switchman/active_record/calculations.rb +93 -50
- data/lib/switchman/active_record/connection_handler.rb +18 -0
- data/lib/switchman/active_record/connection_pool.rb +47 -34
- data/lib/switchman/active_record/database_configurations.rb +32 -6
- data/lib/switchman/active_record/finder_methods.rb +22 -16
- data/lib/switchman/active_record/log_subscriber.rb +3 -6
- data/lib/switchman/active_record/migration.rb +42 -14
- data/lib/switchman/active_record/model_schema.rb +1 -1
- data/lib/switchman/active_record/pending_migration_connection.rb +17 -0
- data/lib/switchman/active_record/persistence.rb +37 -2
- data/lib/switchman/active_record/postgresql_adapter.rb +39 -20
- data/lib/switchman/active_record/predicate_builder.rb +2 -2
- data/lib/switchman/active_record/query_cache.rb +26 -17
- data/lib/switchman/active_record/query_methods.rb +252 -135
- data/lib/switchman/active_record/reflection.rb +10 -3
- data/lib/switchman/active_record/relation.rb +154 -32
- data/lib/switchman/active_record/spawn_methods.rb +3 -7
- data/lib/switchman/active_record/statement_cache.rb +13 -9
- data/lib/switchman/active_record/table_definition.rb +1 -1
- data/lib/switchman/active_record/tasks/database_tasks.rb +6 -1
- data/lib/switchman/active_record/test_fixtures.rb +89 -0
- data/lib/switchman/active_support/cache.rb +25 -4
- data/lib/switchman/arel.rb +20 -7
- data/lib/switchman/call_super.rb +2 -2
- data/lib/switchman/database_server.rb +123 -83
- data/lib/switchman/default_shard.rb +14 -5
- data/lib/switchman/engine.rb +85 -131
- data/lib/switchman/environment.rb +2 -2
- data/lib/switchman/errors.rb +17 -2
- data/lib/switchman/guard_rail/relation.rb +7 -10
- data/lib/switchman/guard_rail.rb +5 -0
- data/lib/switchman/parallel.rb +68 -0
- data/lib/switchman/r_spec_helper.rb +17 -28
- data/lib/switchman/rails.rb +1 -4
- data/{app/models → lib}/switchman/shard.rb +229 -246
- data/lib/switchman/sharded_instrumenter.rb +9 -3
- data/lib/switchman/shared_schema_cache.rb +11 -0
- data/lib/switchman/standard_error.rb +15 -12
- data/lib/switchman/test_helper.rb +3 -3
- data/{app/models → lib}/switchman/unsharded_record.rb +1 -1
- data/lib/switchman/version.rb +1 -1
- data/lib/switchman.rb +46 -12
- data/lib/tasks/switchman.rake +101 -54
- metadata +34 -176
- data/lib/switchman/active_record/association.rb +0 -206
- data/lib/switchman/open4.rb +0 -80
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4676a4e97375d1094b6775fa2a10f907218bbd2cb73f365c801a698822f779a0
|
|
4
|
+
data.tar.gz: 4f88954e9074fcd122ecbb8d965e1b16edb63c63d69fa5311f7e0a97e400c3d5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f043f994b2014ce1c980d6fe73a8137a5b497831bbe87592e0c3140e777d10bcbe1460216e3dcf7cc4d1c71cdbda98d3c2b0e5a6a9859f48120cbc2b9425c24f
|
|
7
|
+
data.tar.gz: 943659f21c0c99d8b1db896009a24601a8e698f13d0b2920e3532829a1bf0b1c5a319487d766d24c47b33a975b597ec4dc57e051ee0376e4b4dc6405515e38c8
|
data/Rakefile
CHANGED
|
@@ -2,37 +2,38 @@
|
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
begin
|
|
5
|
-
require
|
|
5
|
+
require "bundler/setup"
|
|
6
6
|
rescue LoadError
|
|
7
|
-
puts
|
|
7
|
+
puts "You must `gem install bundler` and `bundle install` to run rake tasks"
|
|
8
8
|
end
|
|
9
9
|
begin
|
|
10
|
-
require
|
|
10
|
+
require "rdoc/task"
|
|
11
11
|
rescue LoadError
|
|
12
|
-
require
|
|
13
|
-
require
|
|
12
|
+
require "rdoc/rdoc"
|
|
13
|
+
require "rake/rdoctask"
|
|
14
14
|
RDoc::Task = Rake::RDocTask
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
RDoc::Task.new(:rdoc) do |rdoc|
|
|
18
|
-
rdoc.rdoc_dir =
|
|
19
|
-
rdoc.title =
|
|
20
|
-
rdoc.options <<
|
|
21
|
-
rdoc.rdoc_files.include(
|
|
18
|
+
rdoc.rdoc_dir = "rdoc"
|
|
19
|
+
rdoc.title = "Switchman"
|
|
20
|
+
rdoc.options << "--line-numbers"
|
|
21
|
+
rdoc.rdoc_files.include("lib/**/*.rb")
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
load "./spec/tasks/coverage.rake"
|
|
25
|
+
APP_RAKEFILE = File.expand_path("spec/dummy/Rakefile", __dir__)
|
|
26
|
+
load "rails/tasks/engine.rake"
|
|
26
27
|
|
|
27
28
|
Bundler::GemHelper.install_tasks
|
|
28
29
|
|
|
29
|
-
require
|
|
30
|
+
require "rspec/core/rake_task"
|
|
30
31
|
RSpec::Core::RakeTask.new
|
|
31
32
|
|
|
32
|
-
require
|
|
33
|
+
require "rubocop/rake_task"
|
|
33
34
|
|
|
34
35
|
RuboCop::RakeTask.new do |task|
|
|
35
|
-
task.options = [
|
|
36
|
+
task.options = ["-S"]
|
|
36
37
|
end
|
|
37
38
|
|
|
38
|
-
task default: %i[spec
|
|
39
|
+
task default: %i[spec]
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
class AddDefaultShardIndex < ActiveRecord::Migration[4.2]
|
|
4
4
|
def change
|
|
5
|
-
Switchman::Shard.where(default: nil).update_all(default: false)
|
|
5
|
+
Switchman::Shard.where(default: nil).update_all(default: false) if Switchman::Shard.current.default?
|
|
6
6
|
change_column_default :switchman_shards, :default, false
|
|
7
7
|
change_column_null :switchman_shards, :default, false
|
|
8
|
-
options = if connection.adapter_name ==
|
|
8
|
+
options = if connection.adapter_name == "PostgreSQL"
|
|
9
9
|
{ unique: true, where: '"default"' }
|
|
10
10
|
else
|
|
11
11
|
{}
|
|
@@ -4,7 +4,7 @@ class AddTimestampsToShards < ActiveRecord::Migration[4.2]
|
|
|
4
4
|
disable_ddl_transaction!
|
|
5
5
|
|
|
6
6
|
def change
|
|
7
|
-
add_timestamps :switchman_shards, null: true
|
|
7
|
+
add_timestamps :switchman_shards, null: true, if_not_exists: true
|
|
8
8
|
now = Time.now.utc
|
|
9
9
|
Switchman::Shard.update_all(updated_at: now, created_at: now) if Switchman::Shard.current.default?
|
|
10
10
|
change_column_null :switchman_shards, :updated_at, false
|
|
@@ -3,9 +3,15 @@
|
|
|
3
3
|
class AddUniqueNameIndexes < ActiveRecord::Migration[4.2]
|
|
4
4
|
def change
|
|
5
5
|
add_index :switchman_shards, %i[database_server_id name], unique: true
|
|
6
|
-
add_index :switchman_shards,
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
add_index :switchman_shards,
|
|
7
|
+
:database_server_id,
|
|
8
|
+
unique: true,
|
|
9
|
+
where: "name IS NULL",
|
|
10
|
+
name: "index_switchman_shards_unique_primary_shard"
|
|
11
|
+
add_index :switchman_shards,
|
|
12
|
+
"(true)",
|
|
13
|
+
unique: true,
|
|
14
|
+
where: "database_server_id IS NULL AND name IS NULL",
|
|
15
|
+
name: "index_switchman_shards_unique_primary_db_and_shard"
|
|
10
16
|
end
|
|
11
17
|
end
|
|
@@ -13,8 +13,8 @@ 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=(
|
|
17
|
-
raise NoMethodError
|
|
16
|
+
def cache_store=(cache)
|
|
17
|
+
raise NoMethodError unless cache == ::Rails.cache
|
|
18
18
|
end
|
|
19
19
|
end
|
|
20
20
|
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'switchman/sharded_instrumenter'
|
|
4
|
-
|
|
5
3
|
module Switchman
|
|
6
4
|
module ActiveRecord
|
|
7
5
|
module AbstractAdapter
|
|
8
6
|
module ForeignKeyCheck
|
|
9
7
|
def add_column(table, name, type, limit: nil, **)
|
|
10
|
-
|
|
8
|
+
Switchman.foreign_key_check(name, type, limit:)
|
|
11
9
|
super
|
|
12
10
|
end
|
|
13
11
|
end
|
|
@@ -21,34 +19,29 @@ module Switchman
|
|
|
21
19
|
|
|
22
20
|
def initialize(*args)
|
|
23
21
|
super
|
|
24
|
-
|
|
22
|
+
|
|
23
|
+
@instrumenter = Switchman::ShardedInstrumenter.new(@instrumenter, self) if ::Rails.version < "8.0"
|
|
24
|
+
|
|
25
25
|
@last_query_at = Time.now
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
if ::Rails.version >= "8.0"
|
|
29
|
+
def instrumenter # :nodoc:
|
|
30
|
+
@instrumenter ||= Switchman::ShardedInstrumenter.new(::ActiveSupport::Notifications.instrumenter, self)
|
|
31
|
+
end
|
|
30
32
|
end
|
|
31
33
|
|
|
32
|
-
def
|
|
33
|
-
|
|
34
|
+
def quote_local_table_name(name)
|
|
35
|
+
quote_table_name(name)
|
|
34
36
|
end
|
|
35
37
|
|
|
36
38
|
protected
|
|
37
39
|
|
|
38
|
-
def log(
|
|
40
|
+
def log(...)
|
|
39
41
|
super
|
|
40
42
|
ensure
|
|
41
43
|
@last_query_at = Time.now
|
|
42
44
|
end
|
|
43
|
-
|
|
44
|
-
private
|
|
45
|
-
|
|
46
|
-
def id_value_for_database(value)
|
|
47
|
-
return super unless value.class.sharded_primary_key?
|
|
48
|
-
|
|
49
|
-
# do this the Rails 4.2 way, so that if Shard.current != self.shard, the id gets transposed
|
|
50
|
-
quote(value.id)
|
|
51
|
-
end
|
|
52
45
|
end
|
|
53
46
|
end
|
|
54
47
|
end
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Switchman
|
|
4
|
+
module ActiveRecord
|
|
5
|
+
module Associations
|
|
6
|
+
module Association
|
|
7
|
+
def shard
|
|
8
|
+
reflection.shard(owner)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def build_record(*args)
|
|
12
|
+
shard.activate { super }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def load_target
|
|
16
|
+
shard.activate { super }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def scope
|
|
20
|
+
shard_value = @reflection.options[:multishard] ? @owner : shard
|
|
21
|
+
@owner.shard.activate { super.shard(shard_value, :association) }
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
module CollectionAssociation
|
|
26
|
+
def find_target(async: false)
|
|
27
|
+
shards = if reflection.options[:multishard] && owner.respond_to?(:associated_shards)
|
|
28
|
+
owner.associated_shards
|
|
29
|
+
else
|
|
30
|
+
[shard]
|
|
31
|
+
end
|
|
32
|
+
# activate both the owner and the target's shard category, so that Reflection#join_id_for,
|
|
33
|
+
# when called for the owner, will be returned relative to shard the query will execute on
|
|
34
|
+
Shard.with_each_shard(shards,
|
|
35
|
+
[klass.connection_class_for_self, owner.class.connection_class_for_self].uniq) do
|
|
36
|
+
if reflection.options[:multishard] && owner.respond_to?(:associated_shards) && reflection.has_scope?
|
|
37
|
+
# Prevent duplicate results when reflection has a scope (when it would use the skip_statement_cache? path)
|
|
38
|
+
# otherwise, the super call will set the shard_value to the object, causing it to iterate too many times
|
|
39
|
+
# over the associated shards
|
|
40
|
+
scope.shard(Shard.current(scope.klass.connection_class_for_self), :association).to_a
|
|
41
|
+
elsif ::Rails.version < "8.0"
|
|
42
|
+
super()
|
|
43
|
+
else
|
|
44
|
+
super
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def _create_record(*)
|
|
50
|
+
shard.activate { super }
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
module BelongsToAssociation
|
|
55
|
+
def replace_keys(record, force: false)
|
|
56
|
+
if record&.class&.sharded_column?(reflection.association_primary_key(record.class))
|
|
57
|
+
foreign_id = record[reflection.association_primary_key(record.class)]
|
|
58
|
+
owner[reflection.foreign_key] = Shard.relative_id_for(foreign_id, record.shard, owner.shard)
|
|
59
|
+
else
|
|
60
|
+
super
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def shard
|
|
65
|
+
if @owner.class.sharded_column?(@reflection.foreign_key) &&
|
|
66
|
+
(foreign_id = @owner[@reflection.foreign_key])
|
|
67
|
+
Shard.shard_for(foreign_id, @owner.loaded_from_shard)
|
|
68
|
+
else
|
|
69
|
+
super
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
module ForeignAssociation
|
|
75
|
+
# significant change:
|
|
76
|
+
# * transpose the key to the correct shard
|
|
77
|
+
def set_owner_attributes(record) # rubocop:disable Naming/AccessorMethodName
|
|
78
|
+
return if options[:through]
|
|
79
|
+
|
|
80
|
+
key = owner._read_attribute(reflection.join_foreign_key)
|
|
81
|
+
key = Shard.relative_id_for(key, owner.shard, shard)
|
|
82
|
+
record._write_attribute(reflection.join_primary_key, key)
|
|
83
|
+
|
|
84
|
+
record._write_attribute(reflection.type, owner.class.polymorphic_name) if reflection.type
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
module Extension
|
|
89
|
+
def self.build(_model, _reflection); end
|
|
90
|
+
|
|
91
|
+
def self.valid_options
|
|
92
|
+
[:multishard]
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
::ActiveRecord::Associations::Builder::Association.extensions << Extension
|
|
97
|
+
|
|
98
|
+
module Preloader
|
|
99
|
+
module Association
|
|
100
|
+
# significant changes:
|
|
101
|
+
# * associate shards with records
|
|
102
|
+
# * look on all appropriate shards when loading records
|
|
103
|
+
module LoaderRecords
|
|
104
|
+
def populate_keys_to_load_and_already_loaded_records
|
|
105
|
+
@sharded_keys_to_load = {}
|
|
106
|
+
|
|
107
|
+
loaders.each do |loader|
|
|
108
|
+
multishard = loader.send(:reflection).options[:multishard]
|
|
109
|
+
belongs_to = loader.send(:reflection).macro == :belongs_to
|
|
110
|
+
loader.owners_by_key.each do |key, owners|
|
|
111
|
+
if (loaded_owner = owners.find { |owner| loader.loaded?(owner) })
|
|
112
|
+
already_loaded_records_by_key[key] = loader.target_for(loaded_owner)
|
|
113
|
+
else
|
|
114
|
+
shard_set = @sharded_keys_to_load[key] ||= Set.new
|
|
115
|
+
owner_key_name = loader.send(:owner_key_name)
|
|
116
|
+
owners.each do |owner|
|
|
117
|
+
if multishard && owner.respond_to?(:associated_shards)
|
|
118
|
+
shard_set.merge(owner.associated_shards.map(&:id))
|
|
119
|
+
elsif belongs_to && owner.class.sharded_column?(owner_key_name)
|
|
120
|
+
shard_set.add(Shard.shard_for(owner[owner_key_name], owner.shard).id)
|
|
121
|
+
elsif belongs_to
|
|
122
|
+
shard_set.add(Shard.current.id)
|
|
123
|
+
else
|
|
124
|
+
shard_set.add(owner.shard.id)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
@sharded_keys_to_load.delete_if { |key, _shards| already_loaded_records_by_key.include?(key) }
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def load_records
|
|
135
|
+
ret = []
|
|
136
|
+
|
|
137
|
+
shards_with_keys = @sharded_keys_to_load.each_with_object({}) do |(key, shards), h|
|
|
138
|
+
shards.each { |shard| (h[shard] ||= []) << key }
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
shards_with_keys.each do |shard, keys|
|
|
142
|
+
Shard.lookup(shard).activate do
|
|
143
|
+
scope_was = loader_query.scope
|
|
144
|
+
begin
|
|
145
|
+
loader_query.instance_variable_set(
|
|
146
|
+
:@scope,
|
|
147
|
+
loader_query.scope.shard(
|
|
148
|
+
Shard.current(loader_query.scope.model.connection_class_for_self)
|
|
149
|
+
)
|
|
150
|
+
)
|
|
151
|
+
ret += loader_query.load_records_for_keys(keys) do |record|
|
|
152
|
+
loaders.each { |l| l.set_inverse(record) }
|
|
153
|
+
end
|
|
154
|
+
ensure
|
|
155
|
+
loader_query.instance_variable_set(:@scope, scope_was)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
ret
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Copypasta from Activerecord but with added global_id_for goodness.
|
|
165
|
+
def records_for(ids)
|
|
166
|
+
scope.where(association_key_name => ids).load do |record|
|
|
167
|
+
global_key = if model.connection_class_for_self == UnshardedRecord
|
|
168
|
+
convert_key(record[association_key_name])
|
|
169
|
+
else
|
|
170
|
+
Shard.global_id_for(record[association_key_name], record.shard)
|
|
171
|
+
end
|
|
172
|
+
owner = owners_by_key[convert_key(global_key)].first
|
|
173
|
+
association = owner.association(reflection.name)
|
|
174
|
+
association.set_inverse_instance(record)
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Disabling to keep closer to rails original
|
|
179
|
+
# rubocop:disable Naming/AccessorMethodName
|
|
180
|
+
# significant changes:
|
|
181
|
+
# * globalize the key to lookup
|
|
182
|
+
def set_inverse(record)
|
|
183
|
+
global_key = if model.connection_class_for_self == UnshardedRecord
|
|
184
|
+
convert_key(record[association_key_name])
|
|
185
|
+
else
|
|
186
|
+
Shard.global_id_for(record[association_key_name], record.shard)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
if (owners = owners_by_key[convert_key(global_key)])
|
|
190
|
+
# Processing only the first owner
|
|
191
|
+
# because the record is modified but not an owner
|
|
192
|
+
association = owners.first.association(reflection.name)
|
|
193
|
+
association.set_inverse_instance(record)
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
# rubocop:enable Naming/AccessorMethodName
|
|
197
|
+
|
|
198
|
+
# significant changes:
|
|
199
|
+
# * partition_by_shard the records_for call
|
|
200
|
+
# * re-globalize the fetched owner id before looking up in the map
|
|
201
|
+
# TODO: the ignored param currently loads records; we should probably not waste effort double-loading them
|
|
202
|
+
# Change introduced here: https://github.com/rails/rails/commit/c6c0b2e8af64509b699b782aadfecaa430700ece
|
|
203
|
+
def load_records(raw_records = nil)
|
|
204
|
+
# owners can be duplicated when a relation has a collection association join
|
|
205
|
+
# #compare_by_identity makes such owners different hash keys
|
|
206
|
+
@records_by_owner = {}.compare_by_identity
|
|
207
|
+
|
|
208
|
+
raw_records ||= loader_query.records_for([self])
|
|
209
|
+
|
|
210
|
+
@preloaded_records = raw_records.select do |record|
|
|
211
|
+
assignments = false
|
|
212
|
+
|
|
213
|
+
owner_key = record[association_key_name]
|
|
214
|
+
if owner_key && record.class.sharded_column?(association_key_name)
|
|
215
|
+
owner_key = Shard.global_id_for(owner_key,
|
|
216
|
+
record.shard)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
owners_by_key[convert_key(owner_key)]&.each do |owner|
|
|
220
|
+
entries = (@records_by_owner[owner] ||= [])
|
|
221
|
+
|
|
222
|
+
if reflection.collection? || entries.empty?
|
|
223
|
+
entries << record
|
|
224
|
+
assignments = true
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
assignments
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# significant change: globalize keys on sharded columns
|
|
233
|
+
def owners_by_key
|
|
234
|
+
@owners_by_key ||= owners.each_with_object({}) do |owner, result|
|
|
235
|
+
key = owner[owner_key_name]
|
|
236
|
+
key = Shard.global_id_for(key, owner.shard) if key && owner.class.sharded_column?(owner_key_name)
|
|
237
|
+
key = convert_key(key)
|
|
238
|
+
(result[key] ||= []) << owner if key
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# significant change: don't cache scope (since it could be for different shards)
|
|
243
|
+
def scope
|
|
244
|
+
build_scope
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
module CollectionProxy
|
|
250
|
+
def initialize(*args)
|
|
251
|
+
super
|
|
252
|
+
self.shard_value = scope.shard_value
|
|
253
|
+
self.shard_source_value = :association
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def shard(*)
|
|
257
|
+
scope.shard(*)
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
module AutosaveAssociation
|
|
262
|
+
def association_foreign_key_changed?(reflection, record, key)
|
|
263
|
+
return false if reflection.through_reflection?
|
|
264
|
+
|
|
265
|
+
foreign_key = Array(reflection.foreign_key)
|
|
266
|
+
return false unless foreign_key.all? { |k| record._has_attribute?(k) }
|
|
267
|
+
|
|
268
|
+
# have to use send instead of _read_attribute because sharding
|
|
269
|
+
foreign_key.map { |k| record.send(k) } != Array(key)
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def save_belongs_to_association(reflection)
|
|
273
|
+
association = association_instance_get(reflection.name)
|
|
274
|
+
return unless association&.loaded? && !association.stale_target?
|
|
275
|
+
|
|
276
|
+
record = association.load_target
|
|
277
|
+
return unless record && !record.destroyed?
|
|
278
|
+
|
|
279
|
+
autosave = reflection.options[:autosave]
|
|
280
|
+
|
|
281
|
+
if autosave && record.marked_for_destruction?
|
|
282
|
+
foreign_key = Array(reflection.foreign_key)
|
|
283
|
+
foreign_key.each { |key| self[key] = nil }
|
|
284
|
+
record.destroy
|
|
285
|
+
elsif autosave != false
|
|
286
|
+
saved = record.save(validate: !autosave) if record.new_record? || (autosave && record.changed_for_autosave?)
|
|
287
|
+
|
|
288
|
+
if association.updated?
|
|
289
|
+
primary_key = Array(compute_primary_key(reflection, record)).map(&:to_s)
|
|
290
|
+
foreign_key = Array(reflection.foreign_key)
|
|
291
|
+
|
|
292
|
+
primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
|
|
293
|
+
primary_key_foreign_key_pairs.each do |pk, fk|
|
|
294
|
+
# Notable change: add relative_id_for here
|
|
295
|
+
association_id = if record.class.sharded_column?(pk)
|
|
296
|
+
Shard.relative_id_for(
|
|
297
|
+
record._read_attribute(pk),
|
|
298
|
+
record.shard,
|
|
299
|
+
shard
|
|
300
|
+
)
|
|
301
|
+
else
|
|
302
|
+
record._read_attribute(pk)
|
|
303
|
+
end
|
|
304
|
+
self[fk] = association_id unless self[fk] == association_id
|
|
305
|
+
end
|
|
306
|
+
association.loaded!
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
saved if autosave
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
end
|