switchman 3.4.2 → 3.5.1
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 +15 -14
- data/db/migrate/20180828183945_add_default_shard_index.rb +1 -1
- data/db/migrate/20190114212900_add_unique_name_indexes.rb +10 -4
- data/lib/switchman/active_record/associations.rb +16 -5
- data/lib/switchman/active_record/attribute_methods.rb +67 -22
- data/lib/switchman/active_record/base.rb +32 -12
- data/lib/switchman/active_record/calculations.rb +37 -33
- data/lib/switchman/active_record/connection_pool.rb +4 -2
- data/lib/switchman/active_record/database_configurations.rb +12 -7
- data/lib/switchman/active_record/finder_methods.rb +1 -1
- data/lib/switchman/active_record/log_subscriber.rb +2 -2
- data/lib/switchman/active_record/migration.rb +4 -2
- data/lib/switchman/active_record/persistence.rb +18 -0
- data/lib/switchman/active_record/postgresql_adapter.rb +11 -10
- data/lib/switchman/active_record/query_cache.rb +1 -1
- data/lib/switchman/active_record/query_methods.rb +72 -26
- data/lib/switchman/active_record/relation.rb +13 -7
- data/lib/switchman/active_record/spawn_methods.rb +2 -2
- data/lib/switchman/active_record/statement_cache.rb +2 -2
- data/lib/switchman/active_record/tasks/database_tasks.rb +1 -1
- data/lib/switchman/active_record/test_fixtures.rb +19 -16
- data/lib/switchman/active_support/cache.rb +4 -1
- data/lib/switchman/arel.rb +6 -6
- data/lib/switchman/call_super.rb +1 -1
- data/lib/switchman/database_server.rb +20 -16
- data/lib/switchman/default_shard.rb +3 -3
- data/lib/switchman/engine.rb +33 -18
- data/lib/switchman/environment.rb +2 -2
- data/lib/switchman/errors.rb +4 -1
- data/lib/switchman/guard_rail/relation.rb +1 -1
- data/lib/switchman/parallel.rb +1 -1
- data/lib/switchman/r_spec_helper.rb +10 -10
- data/lib/switchman/shard.rb +30 -23
- data/lib/switchman/sharded_instrumenter.rb +5 -1
- data/lib/switchman/test_helper.rb +1 -1
- data/lib/switchman/version.rb +1 -1
- data/lib/switchman.rb +12 -4
- data/lib/tasks/switchman.rake +40 -37
- metadata +9 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f381ad09908e23b0560c685c530f7ffae1a0adcbbaf6640b5ae4f267dd9323f3
|
4
|
+
data.tar.gz: 1ca68afa9a03005c7c5966f8bc8a6be06f552c086ca469fe4b9eab1a54e819ae
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3340807bd81183c24527ba2739732e6ac5aa413d1184c22d4b1ad2d3cc0674b5222d1cf5ddb7abad158108a35e6c226bf9c7614f55fc890debbee0f20147ef98
|
7
|
+
data.tar.gz: e67e961f5f4085ed7d0b00c272d8f8c911d14b284415e8bab2f528c25177db8504c8c7e8ceafa5e4ac5389e0f3f548f8d069838e21c6f8099864a91d5655d5a9
|
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
39
|
task default: %i[spec]
|
@@ -5,7 +5,7 @@ class AddDefaultShardIndex < ActiveRecord::Migration[4.2]
|
|
5
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
|
{}
|
@@ -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
|
@@ -24,10 +24,15 @@ module Switchman
|
|
24
24
|
|
25
25
|
module CollectionAssociation
|
26
26
|
def find_target
|
27
|
-
shards = reflection.options[:multishard] && owner.respond_to?(:associated_shards)
|
27
|
+
shards = if reflection.options[:multishard] && owner.respond_to?(:associated_shards)
|
28
|
+
owner.associated_shards
|
29
|
+
else
|
30
|
+
[shard]
|
31
|
+
end
|
28
32
|
# activate both the owner and the target's shard category, so that Reflection#join_id_for,
|
29
33
|
# when called for the owner, will be returned relative to shard the query will execute on
|
30
|
-
Shard.with_each_shard(shards,
|
34
|
+
Shard.with_each_shard(shards,
|
35
|
+
[klass.connection_class_for_self, owner.class.connection_class_for_self].uniq) do
|
31
36
|
super
|
32
37
|
end
|
33
38
|
end
|
@@ -128,7 +133,12 @@ module Switchman
|
|
128
133
|
Shard.lookup(shard).activate do
|
129
134
|
scope_was = loader_query.scope
|
130
135
|
begin
|
131
|
-
loader_query.instance_variable_set(
|
136
|
+
loader_query.instance_variable_set(
|
137
|
+
:@scope,
|
138
|
+
loader_query.scope.shard(
|
139
|
+
Shard.current(loader_query.scope.model.connection_class_for_self)
|
140
|
+
)
|
141
|
+
)
|
132
142
|
ret += loader_query.load_records_for_keys(keys) do |record|
|
133
143
|
loaders.each { |l| l.set_inverse(record) }
|
134
144
|
end
|
@@ -186,7 +196,7 @@ module Switchman
|
|
186
196
|
# #compare_by_identity makes such owners different hash keys
|
187
197
|
@records_by_owner = {}.compare_by_identity
|
188
198
|
|
189
|
-
if ::Rails.version >=
|
199
|
+
if ::Rails.version >= "7.0"
|
190
200
|
raw_records ||= loader_query.records_for([self])
|
191
201
|
elsif owner_keys.empty?
|
192
202
|
raw_records ||= []
|
@@ -210,7 +220,8 @@ module Switchman
|
|
210
220
|
relative_owner_keys = partitioned_owners.map do |owner|
|
211
221
|
key = owner[owner_key_name]
|
212
222
|
if key && owner.class.sharded_column?(owner_key_name)
|
213
|
-
key = Shard.relative_id_for(key,
|
223
|
+
key = Shard.relative_id_for(key,
|
224
|
+
owner.shard,
|
214
225
|
Shard.current(klass.connection_class_for_self))
|
215
226
|
end
|
216
227
|
convert_key(key)
|
@@ -31,8 +31,16 @@ module Switchman
|
|
31
31
|
# and not the silly one in AR::AttributeMethods::PrimaryKey
|
32
32
|
return unless sharded_column?(@primary_key)
|
33
33
|
|
34
|
-
class_eval(
|
35
|
-
|
34
|
+
class_eval(
|
35
|
+
build_sharded_getter("id",
|
36
|
+
"_read_attribute(@primary_key)",
|
37
|
+
"::#{connection_class_for_self.name}"),
|
38
|
+
__FILE__,
|
39
|
+
__LINE__
|
40
|
+
)
|
41
|
+
class_eval(build_sharded_setter("id", @primary_key, "::#{connection_class_for_self.name}"),
|
42
|
+
__FILE__,
|
43
|
+
__LINE__)
|
36
44
|
end
|
37
45
|
|
38
46
|
protected
|
@@ -47,7 +55,7 @@ module Switchman
|
|
47
55
|
end
|
48
56
|
|
49
57
|
def define_cached_method(owner, name, namespace:, as:, &block)
|
50
|
-
if ::Rails.version <
|
58
|
+
if ::Rails.version < "7.0"
|
51
59
|
yield owner
|
52
60
|
owner.rename_method(as, name)
|
53
61
|
else
|
@@ -57,7 +65,10 @@ module Switchman
|
|
57
65
|
|
58
66
|
def define_method_global_attribute(attr_name, owner:)
|
59
67
|
if sharded_column?(attr_name)
|
60
|
-
define_cached_method(owner,
|
68
|
+
define_cached_method(owner,
|
69
|
+
"global_#{attr_name}",
|
70
|
+
as: "sharded_global_#{attr_name}",
|
71
|
+
namespace: :switchman) do |batch|
|
61
72
|
batch << <<-RUBY
|
62
73
|
def sharded_global_#{attr_name}
|
63
74
|
raw_value = original_#{attr_name}
|
@@ -69,13 +80,16 @@ module Switchman
|
|
69
80
|
RUBY
|
70
81
|
end
|
71
82
|
else
|
72
|
-
define_method_unsharded_column(attr_name,
|
83
|
+
define_method_unsharded_column(attr_name, "global", owner)
|
73
84
|
end
|
74
85
|
end
|
75
86
|
|
76
87
|
def define_method_local_attribute(attr_name, owner:)
|
77
88
|
if sharded_column?(attr_name)
|
78
|
-
define_cached_method(owner,
|
89
|
+
define_cached_method(owner,
|
90
|
+
"local_#{attr_name}",
|
91
|
+
as: "sharded_local_#{attr_name}",
|
92
|
+
namespace: :switchman) do |batch|
|
79
93
|
batch << <<-RUBY
|
80
94
|
def sharded_local_#{attr_name}
|
81
95
|
raw_value = original_#{attr_name}
|
@@ -85,7 +99,7 @@ module Switchman
|
|
85
99
|
RUBY
|
86
100
|
end
|
87
101
|
else
|
88
|
-
define_method_unsharded_column(attr_name,
|
102
|
+
define_method_unsharded_column(attr_name, "local", owner)
|
89
103
|
end
|
90
104
|
end
|
91
105
|
|
@@ -97,7 +111,13 @@ module Switchman
|
|
97
111
|
if reflection.options[:polymorphic]
|
98
112
|
# a polymorphic association has to be discovered at runtime. This code ends up being something like
|
99
113
|
# context_type.&.constantize&.connection_class_for_self
|
100
|
-
|
114
|
+
<<~RUBY
|
115
|
+
begin
|
116
|
+
read_attribute(:#{reflection.foreign_type})&.constantize&.connection_class_for_self
|
117
|
+
rescue NameError
|
118
|
+
::ActiveRecord::Base
|
119
|
+
end
|
120
|
+
RUBY
|
101
121
|
else
|
102
122
|
# otherwise we can just return a symbol for the statically known type of the association
|
103
123
|
"::#{reflection.klass.connection_class_for_self.name}"
|
@@ -111,9 +131,14 @@ module Switchman
|
|
111
131
|
if sharded_column?(attr_name)
|
112
132
|
reflection = reflection_for_integer_attribute(attr_name)
|
113
133
|
class_name = connection_class_for_self_code_for_reflection(reflection)
|
114
|
-
safe_class_name = class_name.unpack1(
|
115
|
-
define_cached_method(owner,
|
116
|
-
|
134
|
+
safe_class_name = class_name.unpack1("h*")
|
135
|
+
define_cached_method(owner,
|
136
|
+
attr_name,
|
137
|
+
as: "sharded_#{safe_class_name}_#{attr_name}",
|
138
|
+
namespace: :switchman) do |batch|
|
139
|
+
batch << build_sharded_getter("sharded_#{safe_class_name}_#{attr_name}",
|
140
|
+
"original_#{attr_name}",
|
141
|
+
class_name)
|
117
142
|
end
|
118
143
|
else
|
119
144
|
define_cached_method(owner, attr_name, as: "plain_#{attr_name}", namespace: :switchman) do |batch|
|
@@ -153,8 +178,11 @@ module Switchman
|
|
153
178
|
if sharded_column?(attr_name)
|
154
179
|
reflection = reflection_for_integer_attribute(attr_name)
|
155
180
|
class_name = connection_class_for_self_code_for_reflection(reflection)
|
156
|
-
safe_class_name = class_name.unpack1(
|
157
|
-
define_cached_method(owner,
|
181
|
+
safe_class_name = class_name.unpack1("h*")
|
182
|
+
define_cached_method(owner,
|
183
|
+
"#{attr_name}=",
|
184
|
+
as: "sharded_#{safe_class_name}_#{attr_name}=",
|
185
|
+
namespace: :switchman) do |batch|
|
158
186
|
batch << build_sharded_setter("sharded_#{safe_class_name}_#{attr_name}", attr_name, class_name)
|
159
187
|
end
|
160
188
|
else
|
@@ -178,7 +206,10 @@ module Switchman
|
|
178
206
|
|
179
207
|
def define_method_original_attribute(attr_name, owner:)
|
180
208
|
if sharded_column?(attr_name)
|
181
|
-
define_cached_method(owner,
|
209
|
+
define_cached_method(owner,
|
210
|
+
"original_#{attr_name}",
|
211
|
+
as: "sharded_original_#{attr_name}",
|
212
|
+
namespace: :switchman) do |batch|
|
182
213
|
batch << <<-RUBY
|
183
214
|
def sharded_original_#{attr_name}
|
184
215
|
_read_attribute("#{attr_name}") { |n| missing_attribute(n, caller) }
|
@@ -186,14 +217,17 @@ module Switchman
|
|
186
217
|
RUBY
|
187
218
|
end
|
188
219
|
else
|
189
|
-
define_method_unsharded_column(attr_name,
|
220
|
+
define_method_unsharded_column(attr_name, "global", owner)
|
190
221
|
end
|
191
222
|
end
|
192
223
|
|
193
224
|
def define_method_original_attribute=(attr_name, owner:)
|
194
225
|
return unless sharded_column?(attr_name)
|
195
226
|
|
196
|
-
define_cached_method(owner,
|
227
|
+
define_cached_method(owner,
|
228
|
+
"original_#{attr_name}=",
|
229
|
+
as: "sharded_original_#{attr_name}=",
|
230
|
+
namespace: :switchman) do |batch|
|
197
231
|
batch << <<-RUBY
|
198
232
|
def sharded_original_#{attr_name}=(new_value)
|
199
233
|
_write_attribute('#{attr_name}', new_value)
|
@@ -203,9 +237,12 @@ module Switchman
|
|
203
237
|
end
|
204
238
|
|
205
239
|
def define_method_unsharded_column(attr_name, prefix, owner)
|
206
|
-
return if columns_hash["#{prefix}_#{attr_name}"] || attr_name ==
|
240
|
+
return if columns_hash["#{prefix}_#{attr_name}"] || attr_name == "id"
|
207
241
|
|
208
|
-
define_cached_method(owner,
|
242
|
+
define_cached_method(owner,
|
243
|
+
"#{prefix}_#{attr_name}",
|
244
|
+
as: "unsharded_#{prefix}_#{attr_name}",
|
245
|
+
namespace: :switchman) do |batch|
|
209
246
|
batch << <<-RUBY
|
210
247
|
def unsharded_#{prefix}_#{attr_name}
|
211
248
|
raise NoMethodError, "undefined method `#{prefix}_#{attr_name}'; are you missing an association?"
|
@@ -217,8 +254,8 @@ module Switchman
|
|
217
254
|
|
218
255
|
def self.prepended(klass)
|
219
256
|
klass.singleton_class.prepend(ClassMethods)
|
220
|
-
klass.attribute_method_prefix
|
221
|
-
klass.attribute_method_affix prefix:
|
257
|
+
klass.attribute_method_prefix "global_", "local_", "original_"
|
258
|
+
klass.attribute_method_affix prefix: "original_", suffix: "="
|
222
259
|
end
|
223
260
|
|
224
261
|
# these are called if the specific methods haven't been defined yet
|
@@ -226,7 +263,11 @@ module Switchman
|
|
226
263
|
return super unless self.class.sharded_column?(attr_name)
|
227
264
|
|
228
265
|
reflection = self.class.send(:reflection_for_integer_attribute, attr_name)
|
229
|
-
::Switchman::Shard.relative_id_for(
|
266
|
+
::Switchman::Shard.relative_id_for(
|
267
|
+
super,
|
268
|
+
shard,
|
269
|
+
::Switchman::Shard.current(connection_class_for_self_for_reflection(reflection))
|
270
|
+
)
|
230
271
|
end
|
231
272
|
|
232
273
|
def attribute=(attr_name, new_value)
|
@@ -236,7 +277,11 @@ module Switchman
|
|
236
277
|
end
|
237
278
|
|
238
279
|
reflection = self.class.send(:reflection_for_integer_attribute, attr_name)
|
239
|
-
super(::Switchman::Shard.relative_id_for(
|
280
|
+
super(::Switchman::Shard.relative_id_for(
|
281
|
+
new_value,
|
282
|
+
::Switchman::Shard.current(connection_class_for_self_for_reflection(reflection)),
|
283
|
+
shard
|
284
|
+
))
|
240
285
|
end
|
241
286
|
|
242
287
|
def global_attribute(attr_name)
|
@@ -67,7 +67,10 @@ module Switchman
|
|
67
67
|
end
|
68
68
|
|
69
69
|
def establish_connection(config_or_env = nil)
|
70
|
-
|
70
|
+
if config_or_env.is_a?(Symbol) && config_or_env != ::Rails.env.to_sym
|
71
|
+
raise ArgumentError,
|
72
|
+
"establish connection cannot be used on the non-current shard/role"
|
73
|
+
end
|
71
74
|
|
72
75
|
# Ensure we don't randomly surprise change the connection parms associated with a shard/role
|
73
76
|
config_or_env = nil if config_or_env == ::Rails.env.to_sym
|
@@ -82,9 +85,15 @@ module Switchman
|
|
82
85
|
end
|
83
86
|
|
84
87
|
def connected_to_stack
|
85
|
-
|
88
|
+
has_own_stack = if ::Rails.version < "7.0"
|
89
|
+
Thread.current.thread_variable?(:ar_connected_to_stack)
|
90
|
+
else
|
91
|
+
::ActiveSupport::IsolatedExecutionState.key?(:active_record_connected_to_stack)
|
92
|
+
end
|
86
93
|
|
87
94
|
ret = super
|
95
|
+
return ret if has_own_stack
|
96
|
+
|
88
97
|
DatabaseServer.guard_servers
|
89
98
|
ret
|
90
99
|
end
|
@@ -96,10 +105,13 @@ module Switchman
|
|
96
105
|
sharded_role = nil
|
97
106
|
connected_to_stack.reverse_each do |hash|
|
98
107
|
shard_role = hash.dig(:shard_roles, target_shard)
|
99
|
-
|
100
|
-
|
101
|
-
|
108
|
+
unless shard_role &&
|
109
|
+
(hash[:klasses].include?(::ActiveRecord::Base) || hash[:klasses].include?(connection_class_for_self))
|
110
|
+
next
|
102
111
|
end
|
112
|
+
|
113
|
+
sharded_role = shard_role
|
114
|
+
break
|
103
115
|
end
|
104
116
|
# Allow a shard-specific role to be reverted to regular inheritance
|
105
117
|
return sharded_role if sharded_role && sharded_role != :_switchman_inherit
|
@@ -119,13 +131,15 @@ module Switchman
|
|
119
131
|
|
120
132
|
def current_switchman_shard
|
121
133
|
connected_to_stack.reverse_each do |hash|
|
122
|
-
|
134
|
+
if hash[:switchman_shard] && hash[:klasses].include?(connection_class_for_self)
|
135
|
+
return hash[:switchman_shard]
|
136
|
+
end
|
123
137
|
end
|
124
138
|
|
125
139
|
Shard.default
|
126
140
|
end
|
127
141
|
|
128
|
-
if ::Rails.version <
|
142
|
+
if ::Rails.version < "7.0"
|
129
143
|
def connection_class_for_self
|
130
144
|
connection_classes
|
131
145
|
end
|
@@ -172,13 +186,17 @@ module Switchman
|
|
172
186
|
end
|
173
187
|
|
174
188
|
def destroy_shadow_records(target_shards: [Shard.current])
|
175
|
-
raise Errors::ShadowRecordError,
|
176
|
-
|
189
|
+
raise Errors::ShadowRecordError, "Cannot be called on a shadow record." if shadow_record?
|
190
|
+
|
191
|
+
unless self.class.sharded_column?(self.class.primary_key)
|
192
|
+
raise Errors::MethodUnsupportedForUnshardedTableError,
|
193
|
+
"Cannot be called on a record belonging to an unsharded table."
|
194
|
+
end
|
177
195
|
|
178
196
|
Array(target_shards).each do |target_shard|
|
179
197
|
next if target_shard == shard
|
180
198
|
|
181
|
-
target_shard.activate { self.class.where(
|
199
|
+
target_shard.activate { self.class.where("id = ?", global_id).delete_all }
|
182
200
|
end
|
183
201
|
end
|
184
202
|
|
@@ -270,8 +288,10 @@ module Switchman
|
|
270
288
|
|
271
289
|
def id_for_database
|
272
290
|
if self.class.sharded_primary_key?
|
273
|
-
# It's an int, so
|
274
|
-
# In theory we should do
|
291
|
+
# It's an int, so it's safe to just return it without passing it
|
292
|
+
# through anything else. In theory we should do
|
293
|
+
# `@attributes[@primary_key].type.serialize(id)`, but that seems to
|
294
|
+
# have surprising side-effects
|
275
295
|
id
|
276
296
|
else
|
277
297
|
super
|
@@ -28,7 +28,7 @@ module Switchman
|
|
28
28
|
|
29
29
|
def execute_simple_calculation(operation, column_name, distinct)
|
30
30
|
operation = operation.to_s.downcase
|
31
|
-
if operation ==
|
31
|
+
if operation == "average"
|
32
32
|
result = calculate_simple_average(column_name, distinct)
|
33
33
|
else
|
34
34
|
result = activate do |relation|
|
@@ -36,11 +36,11 @@ module Switchman
|
|
36
36
|
end
|
37
37
|
if result.is_a?(Array)
|
38
38
|
case operation
|
39
|
-
when
|
39
|
+
when "count", "sum"
|
40
40
|
result = result.sum
|
41
|
-
when
|
41
|
+
when "minimum"
|
42
42
|
result = result.min
|
43
|
-
when
|
43
|
+
when "maximum"
|
44
44
|
result = result.max
|
45
45
|
end
|
46
46
|
end
|
@@ -52,20 +52,20 @@ module Switchman
|
|
52
52
|
# See activerecord#execute_simple_calculation
|
53
53
|
relation = except(:order)
|
54
54
|
column = aggregate_column(column_name)
|
55
|
-
relation.select_values = [operation_over_aggregate_column(column,
|
56
|
-
operation_over_aggregate_column(column,
|
55
|
+
relation.select_values = [operation_over_aggregate_column(column, "average", distinct).as("average"),
|
56
|
+
operation_over_aggregate_column(column, "count", distinct).as("count")]
|
57
57
|
|
58
58
|
initial_results = relation.activate { |rel| klass.connection.select_all(rel) }
|
59
59
|
if initial_results.is_a?(Array)
|
60
60
|
initial_results.each do |r|
|
61
|
-
r[
|
62
|
-
r[
|
61
|
+
r["average"] = type_cast_calculated_value_switchman(r["average"], column_name, "average")
|
62
|
+
r["count"] = type_cast_calculated_value_switchman(r["count"], column_name, "count")
|
63
63
|
end
|
64
|
-
result = initial_results.
|
65
|
-
|
66
|
-
|
64
|
+
result = initial_results.sum { |r| r["average"] * r["count"] } / initial_results.sum do |r|
|
65
|
+
r["count"]
|
66
|
+
end
|
67
67
|
else
|
68
|
-
result = type_cast_calculated_value_switchman(initial_results.first[
|
68
|
+
result = type_cast_calculated_value_switchman(initial_results.first["average"], column_name, "average")
|
69
69
|
end
|
70
70
|
result
|
71
71
|
end
|
@@ -90,7 +90,7 @@ module Switchman
|
|
90
90
|
row[opts[:aggregate_alias]] = type_cast_calculated_value_switchman(
|
91
91
|
row[opts[:aggregate_alias]], column_name, opts[:operation]
|
92
92
|
)
|
93
|
-
row[
|
93
|
+
row["count"] = row["count"].to_i if opts[:operation] == "average"
|
94
94
|
|
95
95
|
opts[:group_columns].each do |aliaz, _type, group_column_name|
|
96
96
|
if opts[:associated] && (aliaz == opts[:group_aliases].first)
|
@@ -109,7 +109,7 @@ module Switchman
|
|
109
109
|
private
|
110
110
|
|
111
111
|
def type_cast_calculated_value_switchman(value, column_name, operation)
|
112
|
-
if ::Rails.version <
|
112
|
+
if ::Rails.version < "7.0"
|
113
113
|
type_cast_calculated_value(value, operation) do |val|
|
114
114
|
column = aggregate_column(column_name)
|
115
115
|
type ||= column.try(:type_caster) ||
|
@@ -125,7 +125,7 @@ module Switchman
|
|
125
125
|
end
|
126
126
|
|
127
127
|
def column_name_for(field)
|
128
|
-
field.respond_to?(:name) ? field.name.to_s : field.to_s.split(
|
128
|
+
field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last
|
129
129
|
end
|
130
130
|
|
131
131
|
def grouped_calculation_options(operation, column_name, distinct)
|
@@ -135,7 +135,8 @@ module Switchman
|
|
135
135
|
group_attrs = group_values
|
136
136
|
if group_attrs.first.respond_to?(:to_sym)
|
137
137
|
association = klass.reflect_on_association(group_attrs.first.to_sym)
|
138
|
-
|
138
|
+
# only count belongs_to associations
|
139
|
+
associated = group_attrs.size == 1 && association && association.macro == :belongs_to
|
139
140
|
group_fields = Array(associated ? association.foreign_key : group_attrs)
|
140
141
|
else
|
141
142
|
group_fields = group_attrs
|
@@ -146,18 +147,20 @@ module Switchman
|
|
146
147
|
group_columns = group_aliases.zip(group_fields).map do |aliaz, field|
|
147
148
|
[aliaz, type_for(field), column_name_for(field)]
|
148
149
|
end
|
149
|
-
opts.merge!(association: association,
|
150
|
-
|
150
|
+
opts.merge!(association: association,
|
151
|
+
associated: associated,
|
152
|
+
group_aliases: group_aliases,
|
153
|
+
group_columns: group_columns,
|
151
154
|
group_fields: group_fields)
|
152
155
|
|
153
156
|
opts
|
154
157
|
end
|
155
158
|
|
156
159
|
def aggregate_alias_for(operation, column_name)
|
157
|
-
if operation ==
|
158
|
-
|
159
|
-
elsif operation ==
|
160
|
-
|
160
|
+
if operation == "count" && column_name == :all
|
161
|
+
"count_all"
|
162
|
+
elsif operation == "average"
|
163
|
+
"average"
|
161
164
|
else
|
162
165
|
column_alias_for("#{operation} #{column_name}")
|
163
166
|
end
|
@@ -173,13 +176,14 @@ module Switchman
|
|
173
176
|
opts[:distinct]
|
174
177
|
).as(opts[:aggregate_alias])
|
175
178
|
]
|
176
|
-
if opts[:operation] ==
|
179
|
+
if opts[:operation] == "average"
|
177
180
|
# include count in average so we can recalculate the average
|
178
181
|
# across all shards if needed
|
179
182
|
select_values << operation_over_aggregate_column(
|
180
183
|
aggregate_column(opts[:column_name]),
|
181
|
-
|
182
|
-
|
184
|
+
"count",
|
185
|
+
opts[:distinct]
|
186
|
+
).as("count")
|
183
187
|
end
|
184
188
|
|
185
189
|
haves = having_clause.send(:predicates)
|
@@ -205,22 +209,22 @@ module Switchman
|
|
205
209
|
key = key.first if key.size == 1
|
206
210
|
value = row[opts[:aggregate_alias]]
|
207
211
|
|
208
|
-
if opts[:operation] ==
|
212
|
+
if opts[:operation] == "average"
|
209
213
|
if result.key?(key)
|
210
214
|
old_value, old_count = result[key]
|
211
|
-
new_count = old_count + row[
|
212
|
-
new_value = ((old_value * old_count) + (value * row[
|
215
|
+
new_count = old_count + row["count"]
|
216
|
+
new_value = ((old_value * old_count) + (value * row["count"])) / new_count
|
213
217
|
result[key] = [new_value, new_count]
|
214
218
|
else
|
215
|
-
result[key] = [value, row[
|
219
|
+
result[key] = [value, row["count"]]
|
216
220
|
end
|
217
221
|
elsif result.key?(key)
|
218
222
|
case opts[:operation]
|
219
|
-
when
|
223
|
+
when "count", "sum"
|
220
224
|
result[key] += value
|
221
|
-
when
|
225
|
+
when "minimum"
|
222
226
|
result[key] = value if value < result[key]
|
223
|
-
when
|
227
|
+
when "maximum"
|
224
228
|
result[key] = value if value > result[key]
|
225
229
|
end
|
226
230
|
else
|
@@ -228,7 +232,7 @@ module Switchman
|
|
228
232
|
end
|
229
233
|
end
|
230
234
|
|
231
|
-
result.transform_values!(&:first) if opts[:operation] ==
|
235
|
+
result.transform_values!(&:first) if opts[:operation] == "average"
|
232
236
|
|
233
237
|
result
|
234
238
|
end
|
@@ -48,7 +48,9 @@ module Switchman
|
|
48
48
|
end
|
49
49
|
|
50
50
|
def switch_database(conn)
|
51
|
-
|
51
|
+
if !@schemas && conn.adapter_name == "PostgreSQL" && !current_shard.database_server.config[:shard_name]
|
52
|
+
@schemas = conn.current_schemas
|
53
|
+
end
|
52
54
|
|
53
55
|
conn.shard = current_shard
|
54
56
|
end
|
@@ -56,7 +58,7 @@ module Switchman
|
|
56
58
|
private
|
57
59
|
|
58
60
|
def current_shard
|
59
|
-
::Rails.version <
|
61
|
+
(::Rails.version < "7.0") ? connection_klass.current_switchman_shard : connection_class.current_switchman_shard
|
60
62
|
end
|
61
63
|
|
62
64
|
def tls_key
|