switchman 2.0.9 → 2.0.13

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: af9f8893be986771668662ceca8d3e36e28f5babf889a18e1e3efc237f7eae66
4
- data.tar.gz: 675bd5b18076e8da86feb8a6e6271fffc1cd27939dedc1eb51c61bf165122b63
3
+ metadata.gz: de9d4ea9f0368bf1bfe15fdb8dd6bef040a8ec107aed386eaa81f8fa444513df
4
+ data.tar.gz: 71b7905b40d59a4767576552c8f5d16a2b3bdaec6d910f81f18d803a9c916256
5
5
  SHA512:
6
- metadata.gz: aadb4a342920dfe24e6110546380aa83a9aa4f61722e8c4c7265d0bf4494a2e8e04975d00504f7a26f143b87595bdb8c47ea09b4f9344f4925af08055c9d23e5
7
- data.tar.gz: 295a3b4490f511380dcaeb44a6a6985cdf97745e678054eaf46315fc89d3adafd7a84a7a3665ec4521a6baa3b9f787adfccdc6d814baab10999c148b75041773
6
+ metadata.gz: bac07aa4f15521f123a1d95f676448d649c71eddd86fdfbbb57504f59d0c71f9ce69618aa7d1e25e93fdc278708cb2a7385d111417f23a8fc0eb1e9b6f6eefb3
7
+ data.tar.gz: 1c091615da88fe2f54de51de1e740336007fd13d66d0cac0b977d0e2770a6dfacd6a85bd12b689a0c610b1de5daeab4512743f0c50199e0f610806cc2f4dd629
@@ -87,7 +87,7 @@ module Switchman
87
87
  # Copypasta from Activerecord but with added global_id_for goodness.
88
88
  def records_for(ids)
89
89
  scope.where(association_key_name => ids).load do |record|
90
- global_key = if record.class.shard_category == :unsharded
90
+ global_key = if model.shard_category == :unsharded
91
91
  convert_key(record[association_key_name])
92
92
  else
93
93
  Shard.global_id_for(record[association_key_name], record.shard)
@@ -51,7 +51,7 @@ module Switchman
51
51
 
52
52
  def calculate_simple_average(column_name, distinct)
53
53
  # See activerecord#execute_simple_calculation
54
- relation = reorder(nil)
54
+ relation = except(:order)
55
55
  column = aggregate_column(column_name)
56
56
  relation.select_values = [operation_over_aggregate_column(column, "average", distinct).as("average"),
57
57
  operation_over_aggregate_column(column, "count", distinct).as("count")]
@@ -215,6 +215,10 @@ module Switchman
215
215
  name.quoted
216
216
  end
217
217
 
218
+ def with_global_table_name(&block)
219
+ with_local_table_name(false, &block)
220
+ end
221
+
218
222
  def with_local_table_name(enable = true)
219
223
  old_value = @use_local_table_name
220
224
  @use_local_table_name = enable
@@ -15,7 +15,7 @@ module Switchman
15
15
  def convert_to_id(value)
16
16
  case value
17
17
  when ::ActiveRecord::Base
18
- value.send(primary_key)
18
+ value.id
19
19
  else
20
20
  super
21
21
  end
@@ -233,6 +233,10 @@ module Switchman
233
233
  connection.with_local_table_name { super }
234
234
  end
235
235
 
236
+ def table_name_matches?(from)
237
+ connection.with_global_table_name { super }
238
+ end
239
+
236
240
  # semi-private
237
241
  public
238
242
  def transpose_predicates(predicates,
@@ -242,6 +246,18 @@ module Switchman
242
246
  binds: nil,
243
247
  dup_binds_on_mutation: false)
244
248
  result = predicates.map do |predicate|
249
+ if ::Rails.version >= '5.2' && predicate.is_a?(::Arel::Nodes::And)
250
+ new_predicates, _binds = transpose_predicates(predicate.children, source_shard, target_shard,
251
+ remove_nonlocal_primary_keys,
252
+ binds: binds,
253
+ dup_binds_on_mutation: dup_binds_on_mutation)
254
+ next (if new_predicates == predicate.children
255
+ predicate
256
+ else
257
+ ::Arel::Nodes::And.new(new_predicates)
258
+ end)
259
+ end
260
+
245
261
  next predicate unless predicate.is_a?(::Arel::Nodes::Binary)
246
262
  next predicate unless predicate.left.is_a?(::Arel::Attributes::Attribute)
247
263
  relation, column = relation_and_column(predicate.left)
@@ -250,7 +266,7 @@ module Switchman
250
266
  remove = true if type == :primary &&
251
267
  remove_nonlocal_primary_keys &&
252
268
  predicate.left.relation.model == klass &&
253
- predicate.is_a?(::Arel::Nodes::Equality)
269
+ (predicate.is_a?(::Arel::Nodes::Equality) || predicate.is_a?(::Arel::Nodes::In))
254
270
 
255
271
  current_source_shard =
256
272
  if source_shard
@@ -265,7 +281,7 @@ module Switchman
265
281
  new_right_value =
266
282
  case predicate.right
267
283
  when Array
268
- predicate.right.map {|val| transpose_predicate_value(val, current_source_shard, target_shard, type, remove) }
284
+ predicate.right.map {|val| transpose_predicate_value(val, current_source_shard, target_shard, type, remove).presence }.compact
269
285
  else
270
286
  transpose_predicate_value(predicate.right, current_source_shard, target_shard, type, remove)
271
287
  end
@@ -340,7 +356,7 @@ module Switchman
340
356
  value
341
357
  else
342
358
  local_id = Shard.relative_id_for(current_id, current_shard, target_shard) || current_id
343
- local_id = [] if remove_non_local_ids && local_id.is_a?(Integer) && local_id > Shard::IDS_PER_SHARD
359
+ return nil if remove_non_local_ids && local_id.is_a?(Integer) && local_id > Shard::IDS_PER_SHARD
344
360
  if current_id != local_id
345
361
  # make a new bind param
346
362
  ::Arel::Nodes::BindParam.new(query_att.class.new(query_att.name, local_id, query_att.type))
@@ -62,7 +62,7 @@ module Switchman
62
62
  %I{update_all delete_all}.each do |method|
63
63
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
64
64
  def #{method}(*args)
65
- result = self.activate { |relation| relation.call_super(#{method.inspect}, Relation, *args) }
65
+ result = self.activate(unordered: true) { |relation| relation.call_super(#{method.inspect}, Relation, *args) }
66
66
  result = result.sum if result.is_a?(Array)
67
67
  result
68
68
  end
@@ -97,7 +97,7 @@ module Switchman
97
97
  end
98
98
  end
99
99
 
100
- def activate(&block)
100
+ def activate(unordered: false, &block)
101
101
  shards = all_shards
102
102
  if (Array === shards && shards.length == 1)
103
103
  if shards.first == DefaultShard || shards.first == Shard.current(klass.shard_category)
@@ -106,10 +106,64 @@ module Switchman
106
106
  shards.first.activate(klass.shard_category) { yield(self, shards.first) }
107
107
  end
108
108
  else
109
- # TODO: implement local limit to avoid querying extra shards
110
- Shard.with_each_shard(shards, [klass.shard_category]) do
111
- shard(Shard.current(klass.shard_category), :to_a).activate(&block)
109
+ result_count = 0
110
+ can_order = false
111
+ result = Shard.with_each_shard(shards, [klass.shard_category]) do
112
+ # don't even query other shards if we're already past the limit
113
+ next if limit_value && result_count >= limit_value && order_values.empty?
114
+
115
+ relation = shard(Shard.current(klass.shard_category), :to_a)
116
+ # do a minimal query if possible
117
+ relation = relation.limit(limit_value - result_count) if limit_value && !result_count.zero? && order_values.empty?
118
+
119
+ shard_results = relation.activate(&block)
120
+
121
+ if shard_results.present? && !unordered
122
+ can_order ||= can_order_cross_shard_results? unless order_values.empty?
123
+ raise OrderOnMultiShardQuery if !can_order && !order_values.empty? && result_count.positive?
124
+
125
+ result_count += shard_results.is_a?(Array) ? shard_results.length : 1
126
+ end
127
+ shard_results
128
+ end
129
+
130
+ result = reorder_cross_shard_results(result) if can_order
131
+ result.slice!(limit_value..-1) if limit_value
132
+ result
133
+ end
134
+ end
135
+
136
+ def can_order_cross_shard_results?
137
+ # we only presume to be able to post-sort the most basic of orderings
138
+ order_values.all? { |ov| ov.is_a?(::Arel::Nodes::Ordering) && ov.expr.is_a?(::Arel::Attributes::Attribute) }
139
+ end
140
+
141
+ def reorder_cross_shard_results(results)
142
+ results.sort! do |l, r|
143
+ result = 0
144
+ order_values.each do |ov|
145
+ if !l.is_a?(::ActiveRecord::Base)
146
+ a, b = l, r
147
+ elsif l.respond_to?(ov.expr.name)
148
+ a = l.send(ov.expr.name)
149
+ b = r.send(ov.expr.name)
150
+ else
151
+ a = l.attributes[ov.expr.name]
152
+ b = r.attributes[ov.expr.name]
153
+ end
154
+ next if a == b
155
+
156
+ if a.nil? || b.nil?
157
+ result = 1 if a.nil?
158
+ result *= -1 if ov.is_a?(::Arel::Nodes::Descending)
159
+ else
160
+ result = a <=> b
161
+ end
162
+
163
+ result *= -1 if ov.is_a?(::Arel::Nodes::Descending)
164
+ break unless result.zero?
112
165
  end
166
+ result
113
167
  end
114
168
  end
115
169
  end
@@ -142,6 +142,7 @@ module Switchman
142
142
 
143
143
  ::ActiveRecord::Relation::WhereClauseFactory.prepend(ActiveRecord::WhereClauseFactory)
144
144
  ::ActiveRecord::PredicateBuilder::AssociationQueryValue.prepend(ActiveRecord::PredicateBuilder::AssociationQueryValue)
145
+ ::ActiveRecord::PredicateBuilder::PolymorphicArrayValue.prepend(ActiveRecord::PredicateBuilder::AssociationQueryValue)
145
146
  ::ActiveRecord::TypeCaster::Map.include(ActiveRecord::TypeCaster::Map)
146
147
  ::ActiveRecord::TypeCaster::Connection.include(ActiveRecord::TypeCaster::Connection)
147
148
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Switchman
4
- VERSION = "2.0.9"
4
+ VERSION = "2.0.13"
5
5
  end
data/lib/switchman.rb CHANGED
@@ -17,4 +17,6 @@ module Switchman
17
17
  def self.cache=(cache)
18
18
  @cache = cache
19
19
  end
20
+
21
+ class OrderOnMultiShardQuery < RuntimeError; end
20
22
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: switchman
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.9
4
+ version: 2.0.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cody Cutrer
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2021-04-16 00:00:00.000000000 Z
13
+ date: 2021-06-11 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: railties