switchman 2.0.8 → 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: 697ce16e90d84de2535fdbf6dacc40640323dddffa4becb4a3d5e8c3b4d1a67f
4
- data.tar.gz: b94f2c678d8dd4d03c7dd089c441ced5a75923778c972a86a7cc3f20c126bc97
3
+ metadata.gz: de9d4ea9f0368bf1bfe15fdb8dd6bef040a8ec107aed386eaa81f8fa444513df
4
+ data.tar.gz: 71b7905b40d59a4767576552c8f5d16a2b3bdaec6d910f81f18d803a9c916256
5
5
  SHA512:
6
- metadata.gz: 82dff35e613f3f9147e7575f1865402df57375dd8f8396ae6f61ad86cf82ec401098b4210eecabb8596c9d7dbba3a3a3ba87c030d7b17b74aae1605cfc3efac9
7
- data.tar.gz: 8678d9c29a485baf8bc483d3c6717d4e2c88d2de14e51e4356a656b579bd0c65579274e0afd70a94d2fb1bc8c617b44a47d73bb04cd8a0b41f1ad2a378ff7e38
6
+ metadata.gz: bac07aa4f15521f123a1d95f676448d649c71eddd86fdfbbb57504f59d0c71f9ce69618aa7d1e25e93fdc278708cb2a7385d111417f23a8fc0eb1e9b6f6eefb3
7
+ data.tar.gz: 1c091615da88fe2f54de51de1e740336007fd13d66d0cac0b977d0e2770a6dfacd6a85bd12b689a0c610b1de5daeab4512743f0c50199e0f610806cc2f4dd629
@@ -718,6 +718,7 @@ module Switchman
718
718
  Switchman.cache.delete(['shard', id].join('/'))
719
719
  Switchman.cache.delete("default_shard") if default?
720
720
  end
721
+ self.class.clear_cache
721
722
  end
722
723
 
723
724
  def default_name
@@ -10,6 +10,6 @@ class AddDefaultShardIndex < ActiveRecord::Migration[4.2]
10
10
  else
11
11
  {}
12
12
  end
13
- add_index :switchman_shards, :default, options
13
+ add_index :switchman_shards, :default, **options
14
14
  end
15
15
  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
@@ -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")]
@@ -135,7 +135,7 @@ module Switchman
135
135
  primary_pool = retrieve_connection_pool("primary")
136
136
  if primary_pool.is_a?(ConnectionPoolProxy)
137
137
  pool = ConnectionPoolProxy.new(spec_name.to_sym, primary_pool.default_pool, @shard_connection_pools)
138
- pool.schema_cache.copy_values(primary_pool.schema_cache)
138
+ pool.schema_cache.copy_references(primary_pool.schema_cache)
139
139
  pool
140
140
  else
141
141
  primary_pool
@@ -30,10 +30,42 @@ module Switchman
30
30
  end
31
31
 
32
32
  module Migrator
33
+ # significant change: hash shard id, not database name
33
34
  def generate_migrator_advisory_lock_id
34
- shard_name_hash = Zlib.crc32("#{Shard.current.id}:#{Shard.current.name}")
35
+ shard_name_hash = Zlib.crc32(Shard.current.name)
35
36
  ::ActiveRecord::Migrator::MIGRATOR_SALT * shard_name_hash
36
37
  end
38
+
39
+ if ::Rails.version >= '6.0'
40
+ # copy/paste from Rails 6.1
41
+ def with_advisory_lock
42
+ lock_id = generate_migrator_advisory_lock_id
43
+
44
+ with_advisory_lock_connection do |connection|
45
+ got_lock = connection.get_advisory_lock(lock_id)
46
+ raise ::ActiveRecord::ConcurrentMigrationError unless got_lock
47
+ load_migrated # reload schema_migrations to be sure it wasn't changed by another process before we got the lock
48
+ yield
49
+ ensure
50
+ if got_lock && !connection.release_advisory_lock(lock_id)
51
+ raise ::ActiveRecord::ConcurrentMigrationError.new(
52
+ ::ActiveRecord::ConcurrentMigrationError::RELEASE_LOCK_FAILED_MESSAGE
53
+ )
54
+ end
55
+ end
56
+ end
57
+
58
+ # significant change: strip out prefer_secondary from config
59
+ def with_advisory_lock_connection
60
+ pool = ::ActiveRecord::ConnectionAdapters::ConnectionHandler.new.establish_connection(
61
+ ::ActiveRecord::Base.connection_config.except(:prefer_secondary)
62
+ )
63
+
64
+ pool.with_connection { |connection| yield(connection) }
65
+ ensure
66
+ pool&.disconnect!
67
+ end
68
+ end
37
69
  end
38
70
 
39
71
  module MigrationContext
@@ -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
@@ -84,6 +84,10 @@ module Switchman
84
84
  @schema_cache
85
85
  end
86
86
 
87
+ def set_schema_cache(cache)
88
+ @schema_cache.copy_values(cache)
89
+ end
90
+
87
91
  %w{release_connection
88
92
  disconnect!
89
93
  flush!
@@ -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
 
@@ -5,14 +5,22 @@ module Switchman
5
5
  delegate :connection, to: :pool
6
6
  attr_reader :pool
7
7
 
8
+ SHARED_IVS = %i{@columns @columns_hash @primary_keys @data_sources @indexes}.freeze
9
+
8
10
  def initialize(pool)
9
11
  @pool = pool
10
12
  super(nil)
11
13
  end
12
14
 
13
15
  def copy_values(other_cache)
16
+ SHARED_IVS.each do |iv|
17
+ instance_variable_get(iv).replace(other_cache.instance_variable_get(iv))
18
+ end
19
+ end
20
+
21
+ def copy_references(other_cache)
14
22
  # use the same cached values but still fall back to the correct pool
15
- [:@columns, :@columns_hash, :@primary_keys, :@data_sources].each do |iv|
23
+ SHARED_IVS.each do |iv|
16
24
  instance_variable_set(iv, other_cache.instance_variable_get(iv))
17
25
  end
18
26
  end
@@ -24,7 +24,7 @@ module Switchman
24
24
  else
25
25
  server1 = Shard.default.database_server
26
26
  end
27
- server2 = DatabaseServer.create(Shard.default.database_server.config)
27
+ server2 = DatabaseServer.create(Shard.default.database_server.config.merge(server2: true))
28
28
 
29
29
  if server1 == Shard.default.database_server && server1.config[:shard1] && server1.config[:shard2]
30
30
  # look for the shards in the db already
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Switchman
4
- VERSION = "2.0.8"
4
+ VERSION = "2.0.13"
5
5
  end
metadata CHANGED
@@ -1,16 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: switchman
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.8
4
+ version: 2.0.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cody Cutrer
8
8
  - James Williams
9
9
  - Jacob Fugal
10
- autorequire:
10
+ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2021-04-01 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
@@ -257,7 +257,7 @@ homepage: http://www.instructure.com/
257
257
  licenses:
258
258
  - MIT
259
259
  metadata: {}
260
- post_install_message:
260
+ post_install_message:
261
261
  rdoc_options: []
262
262
  require_paths:
263
263
  - lib
@@ -272,8 +272,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
272
272
  - !ruby/object:Gem::Version
273
273
  version: '0'
274
274
  requirements: []
275
- rubygems_version: 3.0.3
276
- signing_key:
275
+ rubygems_version: 3.2.15
276
+ signing_key:
277
277
  specification_version: 4
278
278
  summary: Rails sharding magic
279
279
  test_files: []