switchman 3.0.1 → 3.0.3

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: 8cfc736ed07b0e8b80b62928efb9e523a6e8e6ab31841ef4f7022ffbba7a7b57
4
- data.tar.gz: 1879cbef2d1e8ab2d451fcf31ebe3b0a37c10e27eb1099ca96e73f58446adfae
3
+ metadata.gz: e5760f08a540406e8e7d546f0104c1ff8833ce7610daba34810b53ffe5caa295
4
+ data.tar.gz: 4bf65b183425addbec13ae77d9e4f1e3b89244afbcf9625a7e86717e216ce778
5
5
  SHA512:
6
- metadata.gz: c82b93120355776072460d8e2ec1a648ed76454ed178ee3a102b66303dc9716b23bdea855a8047995c454b2528bbe580ceccba43a36edc4afb2d4458e984b493
7
- data.tar.gz: a97fb461a01e06ad8498635382d316ca84e0830a54984e45ce4c1cb575c3ff11e121185c67618246d175ff61eee2c1931acc5b48876235d65b6802a349621077
6
+ metadata.gz: e3e6467f46cb98d78e53b96b1661dbb0cfb59efd25cade9d5f1f2bde4ba067478871c42f91bd06c11c42ed6153c37fca9ef0a20555f3598f910dbda17ce9b42c
7
+ data.tar.gz: eba93dc04390850eba1a7224bcf5f8fbd5b272133cbac94211c512df023a3e9a7d3951267b55bbc831012f03d443ff6705f74c601bc41a05efb389ecae4240c2
@@ -85,7 +85,7 @@ module Switchman
85
85
  # Copypasta from Activerecord but with added global_id_for goodness.
86
86
  def records_for(ids)
87
87
  scope.where(association_key_name => ids).load do |record|
88
- global_key = if record.class.connection_classes == UnshardedRecord
88
+ global_key = if model.connection_classes == UnshardedRecord
89
89
  convert_key(record[association_key_name])
90
90
  else
91
91
  Shard.global_id_for(record[association_key_name], record.shard)
@@ -50,7 +50,7 @@ module Switchman
50
50
 
51
51
  def calculate_simple_average(column_name, distinct)
52
52
  # See activerecord#execute_simple_calculation
53
- relation = reorder(nil)
53
+ relation = except(:order)
54
54
  column = aggregate_column(column_name)
55
55
  relation.select_values = [operation_over_aggregate_column(column, 'average', distinct).as('average'),
56
56
  operation_over_aggregate_column(column, 'count', distinct).as('count')]
@@ -87,6 +87,10 @@ module Switchman
87
87
  name.quoted
88
88
  end
89
89
 
90
+ def with_global_table_name(&block)
91
+ with_local_table_name(false, &block)
92
+ end
93
+
90
94
  def with_local_table_name(enable = true) # rubocop:disable Style/OptionalBooleanParameter
91
95
  old_value = @use_local_table_name
92
96
  @use_local_table_name = enable
@@ -239,6 +239,10 @@ module Switchman
239
239
  connection.with_local_table_name { super }
240
240
  end
241
241
 
242
+ def table_name_matches?(from)
243
+ connection.with_global_table_name { super }
244
+ end
245
+
242
246
  def transpose_predicates(predicates,
243
247
  source_shard,
244
248
  target_shard,
@@ -281,7 +285,7 @@ module Switchman
281
285
  remove = true if type == :primary &&
282
286
  remove_nonlocal_primary_keys &&
283
287
  predicate.left.relation.klass == klass &&
284
- predicate.is_a?(::Arel::Nodes::Equality)
288
+ (predicate.is_a?(::Arel::Nodes::Equality) || predicate.is_a?(::Arel::Nodes::HomogeneousIn))
285
289
 
286
290
  current_source_shard =
287
291
  if source_shard
@@ -301,7 +305,7 @@ module Switchman
301
305
  new_right_value =
302
306
  case right
303
307
  when Array
304
- right.map { |val| transpose_predicate_value(val, current_source_shard, target_shard, type, remove) }
308
+ right.map { |val| transpose_predicate_value(val, current_source_shard, target_shard, type, remove).presence }.compact
305
309
  else
306
310
  transpose_predicate_value(right, current_source_shard, target_shard, type, remove)
307
311
  end
@@ -315,7 +319,13 @@ module Switchman
315
319
  predicate.class.new(predicate.left, right.class.new(new_right_value, right.attribute))
316
320
  end
317
321
  elsif predicate.is_a?(::Arel::Nodes::HomogeneousIn)
318
- predicate.class.new(new_right_value, predicate.attribute, predicate.type)
322
+ # switch to a regular In, so that Relation::WhereClause#contradiction? knows about it
323
+ if new_right_value.empty?
324
+ klass = predicate.type == :in ? ::Arel::Nodes::In : ::Arel::Nodes::NotIn
325
+ klass.new(predicate.attribute, new_right_value)
326
+ else
327
+ predicate.class.new(new_right_value, predicate.attribute, predicate.type)
328
+ end
319
329
  else
320
330
  predicate.class.new(predicate.left, new_right_value)
321
331
  end
@@ -112,10 +112,57 @@ module Switchman
112
112
  shards.first.activate(klass.connection_classes) { yield(self, shards.first) }
113
113
  end
114
114
  else
115
- # TODO: implement local limit to avoid querying extra shards
116
- Shard.with_each_shard(shards, [klass.connection_classes]) do
117
- shard(Shard.current(klass.connection_classes), :to_a).activate(&block)
115
+ result_count = 0
116
+ can_order = false
117
+ result = Shard.with_each_shard(shards, [klass.connection_classes]) do
118
+ # don't even query other shards if we're already past the limit
119
+ next if limit_value && result_count >= limit_value && order_values.empty?
120
+
121
+ relation = shard(Shard.current(klass.connection_classes), :to_a)
122
+ # do a minimal query if possible
123
+ relation = relation.limit(limit_value - result_count) if limit_value && !result_count.zero? && order_values.empty?
124
+
125
+ shard_results = relation.activate(&block)
126
+
127
+ if shard_results.present?
128
+ can_order ||= can_order_cross_shard_results? unless order_values.empty?
129
+ raise OrderOnMultiShardQuery if !can_order && !order_values.empty? && result_count.positive?
130
+
131
+ result_count += shard_results.is_a?(Array) ? shard_results.length : 1
132
+ end
133
+ shard_results
134
+ end
135
+
136
+ result = reorder_cross_shard_results(result) if can_order
137
+ result.slice!(limit_value..-1) if limit_value
138
+ result
139
+ end
140
+ end
141
+
142
+ def can_order_cross_shard_results?
143
+ # we only presume to be able to post-sort the most basic of orderings
144
+ order_values.all? { |ov| ov.is_a?(::Arel::Nodes::Ordering) && ov.expr.is_a?(::Arel::Attributes::Attribute) }
145
+ end
146
+
147
+ def reorder_cross_shard_results(results)
148
+ results.sort! do |l, r|
149
+ result = 0
150
+ order_values.each do |ov|
151
+ a = l.attribute(ov.expr.name)
152
+ b = r.attribute(ov.expr.name)
153
+ next if a == b
154
+
155
+ if a.nil? || b.nil?
156
+ result = 1 if a.nil?
157
+ result *= -1 if ov.is_a?(::Arel::Nodes::Descending)
158
+ else
159
+ result = a <=> b
160
+ end
161
+
162
+ result *= -1 if ov.is_a?(::Arel::Nodes::Descending)
163
+ break unless result.zero?
118
164
  end
165
+ result
119
166
  end
120
167
  end
121
168
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Switchman
4
- VERSION = '3.0.1'
4
+ VERSION = '3.0.3'
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: 3.0.1
4
+ version: 3.0.3
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-05-24 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: activerecord