switchman 3.0.2 → 3.1.0

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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +1 -1
  3. data/db/migrate/20180828183945_add_default_shard_index.rb +1 -1
  4. data/db/migrate/20180828192111_add_timestamps_to_shards.rb +1 -1
  5. data/lib/switchman/action_controller/caching.rb +2 -2
  6. data/lib/switchman/active_record/abstract_adapter.rb +2 -13
  7. data/lib/switchman/active_record/associations.rb +223 -0
  8. data/lib/switchman/active_record/attribute_methods.rb +144 -63
  9. data/lib/switchman/active_record/base.rb +100 -43
  10. data/lib/switchman/active_record/calculations.rb +12 -5
  11. data/lib/switchman/active_record/connection_pool.rb +9 -31
  12. data/lib/switchman/active_record/database_configurations.rb +18 -2
  13. data/lib/switchman/active_record/finder_methods.rb +2 -2
  14. data/lib/switchman/active_record/migration.rb +7 -4
  15. data/lib/switchman/active_record/model_schema.rb +1 -1
  16. data/lib/switchman/active_record/persistence.rb +7 -2
  17. data/lib/switchman/active_record/postgresql_adapter.rb +6 -2
  18. data/lib/switchman/active_record/predicate_builder.rb +1 -1
  19. data/lib/switchman/active_record/query_methods.rb +27 -14
  20. data/lib/switchman/active_record/reflection.rb +1 -1
  21. data/lib/switchman/active_record/relation.rb +25 -24
  22. data/lib/switchman/active_record/statement_cache.rb +2 -2
  23. data/lib/switchman/active_record/table_definition.rb +1 -1
  24. data/lib/switchman/active_record/test_fixtures.rb +43 -0
  25. data/lib/switchman/active_support/cache.rb +16 -0
  26. data/lib/switchman/arel.rb +28 -6
  27. data/lib/switchman/database_server.rb +71 -65
  28. data/lib/switchman/default_shard.rb +0 -2
  29. data/lib/switchman/engine.rb +67 -125
  30. data/lib/switchman/errors.rb +4 -2
  31. data/lib/switchman/guard_rail/relation.rb +6 -9
  32. data/lib/switchman/guard_rail.rb +5 -0
  33. data/lib/switchman/parallel.rb +68 -0
  34. data/lib/switchman/r_spec_helper.rb +5 -17
  35. data/lib/switchman/rails.rb +1 -4
  36. data/{app/models → lib}/switchman/shard.rb +61 -188
  37. data/lib/switchman/sharded_instrumenter.rb +1 -1
  38. data/lib/switchman/standard_error.rb +11 -12
  39. data/{app/models → lib}/switchman/unsharded_record.rb +1 -1
  40. data/lib/switchman/version.rb +1 -1
  41. data/lib/switchman.rb +22 -2
  42. data/lib/tasks/switchman.rake +24 -13
  43. metadata +24 -22
  44. data/lib/switchman/active_record/association.rb +0 -206
  45. data/lib/switchman/open4.rb +0 -80
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.2
4
+ version: 3.1.0
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-06-09 00:00:00.000000000 Z
13
+ date: 2022-06-02 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activerecord
@@ -18,48 +18,48 @@ dependencies:
18
18
  requirements:
19
19
  - - ">="
20
20
  - !ruby/object:Gem::Version
21
- version: '6.1'
21
+ version: 6.1.4
22
22
  - - "<"
23
23
  - !ruby/object:Gem::Version
24
- version: '6.2'
24
+ version: '7.1'
25
25
  type: :runtime
26
26
  prerelease: false
27
27
  version_requirements: !ruby/object:Gem::Requirement
28
28
  requirements:
29
29
  - - ">="
30
30
  - !ruby/object:Gem::Version
31
- version: '6.1'
31
+ version: 6.1.4
32
32
  - - "<"
33
33
  - !ruby/object:Gem::Version
34
- version: '6.2'
34
+ version: '7.1'
35
35
  - !ruby/object:Gem::Dependency
36
36
  name: guardrail
37
37
  requirement: !ruby/object:Gem::Requirement
38
38
  requirements:
39
39
  - - "~>"
40
40
  - !ruby/object:Gem::Version
41
- version: 3.0.0
41
+ version: 3.0.1
42
42
  type: :runtime
43
43
  prerelease: false
44
44
  version_requirements: !ruby/object:Gem::Requirement
45
45
  requirements:
46
46
  - - "~>"
47
47
  - !ruby/object:Gem::Version
48
- version: 3.0.0
48
+ version: 3.0.1
49
49
  - !ruby/object:Gem::Dependency
50
- name: open4
50
+ name: parallel
51
51
  requirement: !ruby/object:Gem::Requirement
52
52
  requirements:
53
53
  - - "~>"
54
54
  - !ruby/object:Gem::Version
55
- version: 1.3.0
55
+ version: '1.22'
56
56
  type: :runtime
57
57
  prerelease: false
58
58
  version_requirements: !ruby/object:Gem::Requirement
59
59
  requirements:
60
60
  - - "~>"
61
61
  - !ruby/object:Gem::Version
62
- version: 1.3.0
62
+ version: '1.22'
63
63
  - !ruby/object:Gem::Dependency
64
64
  name: railties
65
65
  requirement: !ruby/object:Gem::Requirement
@@ -69,7 +69,7 @@ dependencies:
69
69
  version: '6.1'
70
70
  - - "<"
71
71
  - !ruby/object:Gem::Version
72
- version: '6.2'
72
+ version: '7.1'
73
73
  type: :runtime
74
74
  prerelease: false
75
75
  version_requirements: !ruby/object:Gem::Requirement
@@ -79,21 +79,21 @@ dependencies:
79
79
  version: '6.1'
80
80
  - - "<"
81
81
  - !ruby/object:Gem::Version
82
- version: '6.2'
82
+ version: '7.1'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: appraisal
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '2.1'
89
+ version: 2.3.0
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: '2.1'
96
+ version: 2.3.0
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: byebug
99
99
  requirement: !ruby/object:Gem::Requirement
@@ -242,8 +242,6 @@ extensions: []
242
242
  extra_rdoc_files: []
243
243
  files:
244
244
  - Rakefile
245
- - app/models/switchman/shard.rb
246
- - app/models/switchman/unsharded_record.rb
247
245
  - db/migrate/20130328212039_create_switchman_shards.rb
248
246
  - db/migrate/20130328224244_create_default_shard.rb
249
247
  - db/migrate/20161206323434_add_back_default_string_limits_switchman.rb
@@ -253,7 +251,7 @@ files:
253
251
  - lib/switchman.rb
254
252
  - lib/switchman/action_controller/caching.rb
255
253
  - lib/switchman/active_record/abstract_adapter.rb
256
- - lib/switchman/active_record/association.rb
254
+ - lib/switchman/active_record/associations.rb
257
255
  - lib/switchman/active_record/attribute_methods.rb
258
256
  - lib/switchman/active_record/base.rb
259
257
  - lib/switchman/active_record/calculations.rb
@@ -275,6 +273,7 @@ files:
275
273
  - lib/switchman/active_record/statement_cache.rb
276
274
  - lib/switchman/active_record/table_definition.rb
277
275
  - lib/switchman/active_record/tasks/database_tasks.rb
276
+ - lib/switchman/active_record/test_fixtures.rb
278
277
  - lib/switchman/active_record/type_caster.rb
279
278
  - lib/switchman/active_support/cache.rb
280
279
  - lib/switchman/arel.rb
@@ -286,18 +285,21 @@ files:
286
285
  - lib/switchman/errors.rb
287
286
  - lib/switchman/guard_rail.rb
288
287
  - lib/switchman/guard_rail/relation.rb
289
- - lib/switchman/open4.rb
288
+ - lib/switchman/parallel.rb
290
289
  - lib/switchman/r_spec_helper.rb
291
290
  - lib/switchman/rails.rb
291
+ - lib/switchman/shard.rb
292
292
  - lib/switchman/sharded_instrumenter.rb
293
293
  - lib/switchman/standard_error.rb
294
294
  - lib/switchman/test_helper.rb
295
+ - lib/switchman/unsharded_record.rb
295
296
  - lib/switchman/version.rb
296
297
  - lib/tasks/switchman.rake
297
298
  homepage: http://www.instructure.com/
298
299
  licenses:
299
300
  - MIT
300
- metadata: {}
301
+ metadata:
302
+ rubygems_mfa_required: 'true'
301
303
  post_install_message:
302
304
  rdoc_options: []
303
305
  require_paths:
@@ -306,14 +308,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
306
308
  requirements:
307
309
  - - ">="
308
310
  - !ruby/object:Gem::Version
309
- version: '2.6'
311
+ version: '2.7'
310
312
  required_rubygems_version: !ruby/object:Gem::Requirement
311
313
  requirements:
312
314
  - - ">="
313
315
  - !ruby/object:Gem::Version
314
316
  version: '0'
315
317
  requirements: []
316
- rubygems_version: 3.2.15
318
+ rubygems_version: 3.1.6
317
319
  signing_key:
318
320
  specification_version: 4
319
321
  summary: Rails sharding magic
@@ -1,206 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Switchman
4
- module ActiveRecord
5
- module Association
6
- def shard
7
- reflection.shard(owner)
8
- end
9
-
10
- def build_record(*args)
11
- shard.activate { super }
12
- end
13
-
14
- def load_target
15
- shard.activate { super }
16
- end
17
-
18
- def scope
19
- shard_value = @reflection.options[:multishard] ? @owner : shard
20
- @owner.shard.activate { super.shard(shard_value, :association) }
21
- end
22
- end
23
-
24
- module CollectionAssociation
25
- def find_target
26
- shards = reflection.options[:multishard] && owner.respond_to?(:associated_shards) ? owner.associated_shards : [shard]
27
- # activate both the owner and the target's shard category, so that Reflection#join_id_for,
28
- # when called for the owner, will be returned relative to shard the query will execute on
29
- Shard.with_each_shard(shards, [klass.connection_classes, owner.class.connection_classes].uniq) do
30
- super
31
- end
32
- end
33
-
34
- def _create_record(*)
35
- shard.activate { super }
36
- end
37
- end
38
-
39
- module BelongsToAssociation
40
- def replace_keys(record, force: false)
41
- if record&.class&.sharded_column?(reflection.association_primary_key(record.class))
42
- foreign_id = record[reflection.association_primary_key(record.class)]
43
- owner[reflection.foreign_key] = Shard.relative_id_for(foreign_id, record.shard, owner.shard)
44
- else
45
- super
46
- end
47
- end
48
-
49
- def shard
50
- if @owner.class.sharded_column?(@reflection.foreign_key) &&
51
- (foreign_id = @owner[@reflection.foreign_key])
52
- Shard.shard_for(foreign_id, @owner.shard)
53
- else
54
- super
55
- end
56
- end
57
- end
58
-
59
- module ForeignAssociation
60
- # significant change:
61
- # * transpose the key to the correct shard
62
- def set_owner_attributes(record) # rubocop:disable Naming/AccessorMethodName
63
- return if options[:through]
64
-
65
- key = owner._read_attribute(reflection.join_foreign_key)
66
- key = Shard.relative_id_for(key, owner.shard, shard)
67
- record._write_attribute(reflection.join_primary_key, key)
68
-
69
- record._write_attribute(reflection.type, owner.class.polymorphic_name) if reflection.type
70
- end
71
- end
72
-
73
- module Extension
74
- def self.build(_model, _reflection); end
75
-
76
- def self.valid_options
77
- [:multishard]
78
- end
79
- end
80
-
81
- ::ActiveRecord::Associations::Builder::Association.extensions << Extension
82
-
83
- module Preloader
84
- module Association
85
- # Copypasta from Activerecord but with added global_id_for goodness.
86
- def records_for(ids)
87
- scope.where(association_key_name => ids).load do |record|
88
- global_key = if model.connection_classes == UnshardedRecord
89
- convert_key(record[association_key_name])
90
- else
91
- Shard.global_id_for(record[association_key_name], record.shard)
92
- end
93
- owner = owners_by_key[convert_key(global_key)].first
94
- association = owner.association(reflection.name)
95
- association.set_inverse_instance(record)
96
- end
97
- end
98
-
99
- # significant changes:
100
- # * partition_by_shard the records_for call
101
- # * re-globalize the fetched owner id before looking up in the map
102
- def load_records
103
- # owners can be duplicated when a relation has a collection association join
104
- # #compare_by_identity makes such owners different hash keys
105
- @records_by_owner = {}.compare_by_identity
106
-
107
- if owner_keys.empty?
108
- raw_records = []
109
- else
110
- # determine the shard to search for each owner
111
- if reflection.macro == :belongs_to
112
- # for belongs_to, it's the shard of the foreign_key
113
- partition_proc = lambda do |owner|
114
- if owner.class.sharded_column?(owner_key_name)
115
- Shard.shard_for(owner[owner_key_name], owner.shard)
116
- else
117
- Shard.current
118
- end
119
- end
120
- elsif !reflection.options[:multishard]
121
- # for non-multishard associations, it's *just* the owner's shard
122
- partition_proc = ->(owner) { owner.shard }
123
- end
124
-
125
- raw_records = Shard.partition_by_shard(owners, partition_proc) do |partitioned_owners|
126
- relative_owner_keys = partitioned_owners.map do |owner|
127
- key = owner[owner_key_name]
128
- if key && owner.class.sharded_column?(owner_key_name)
129
- key = Shard.relative_id_for(key, owner.shard,
130
- Shard.current(klass.connection_classes))
131
- end
132
- convert_key(key)
133
- end
134
- relative_owner_keys.compact!
135
- relative_owner_keys.uniq!
136
- records_for(relative_owner_keys)
137
- end
138
- end
139
-
140
- @preloaded_records = raw_records.select do |record|
141
- assignments = false
142
-
143
- owner_key = record[association_key_name]
144
- if owner_key && record.class.sharded_column?(association_key_name)
145
- owner_key = Shard.global_id_for(owner_key,
146
- record.shard)
147
- end
148
-
149
- owners_by_key[convert_key(owner_key)].each do |owner|
150
- entries = (@records_by_owner[owner] ||= [])
151
-
152
- if reflection.collection? || entries.empty?
153
- entries << record
154
- assignments = true
155
- end
156
- end
157
-
158
- assignments
159
- end
160
- end
161
-
162
- # significant change: globalize keys on sharded columns
163
- def owners_by_key
164
- @owners_by_key ||= owners.each_with_object({}) do |owner, result|
165
- key = owner[owner_key_name]
166
- key = Shard.global_id_for(key, owner.shard) if key && owner.class.sharded_column?(owner_key_name)
167
- key = convert_key(key)
168
- (result[key] ||= []) << owner if key
169
- end
170
- end
171
-
172
- # significant change: don't cache scope (since it could be for different shards)
173
- def scope
174
- build_scope
175
- end
176
- end
177
- end
178
-
179
- module CollectionProxy
180
- def initialize(*args)
181
- super
182
- self.shard_value = scope.shard_value
183
- self.shard_source_value = :association
184
- end
185
-
186
- def shard(*args)
187
- scope.shard(*args)
188
- end
189
- end
190
-
191
- module AutosaveAssociation
192
- def record_changed?(reflection, record, key)
193
- record.new_record? ||
194
- (record.has_attribute?(reflection.foreign_key) && record.send(reflection.foreign_key) != key) || # have to use send instead of [] because sharding
195
- record.attribute_changed?(reflection.foreign_key)
196
- end
197
-
198
- def save_belongs_to_association(reflection)
199
- # this seems counter-intuitive, but the autosave code will assign to attribute bypassing switchman,
200
- # after reading the id attribute _without_ bypassing switchman. So we need Shard.current for the
201
- # category of the associated record to match Shard.current for the category of self
202
- shard.activate(connection_classes_for_reflection(reflection)) { super }
203
- end
204
- end
205
- end
206
- end
@@ -1,80 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'open4'
4
-
5
- # This fixes a bug with exception handling,
6
- # see https://github.com/ahoward/open4/pull/30
7
- module Open4
8
- def self.do_popen(b = nil, exception_propagation_at = nil, closefds=false, &cmd)
9
- pw, pr, pe, ps = IO.pipe, IO.pipe, IO.pipe, IO.pipe
10
-
11
- verbose = $VERBOSE
12
- begin
13
- $VERBOSE = nil
14
-
15
- cid = fork {
16
- if closefds
17
- exlist = [0, 1, 2] | [pw,pr,pe,ps].map{|p| [p.first.fileno, p.last.fileno] }.flatten
18
- ObjectSpace.each_object(IO){|io|
19
- io.close if (not io.closed?) and (not exlist.include? io.fileno) rescue nil
20
- }
21
- end
22
-
23
- pw.last.close
24
- STDIN.reopen pw.first
25
- pw.first.close
26
-
27
- pr.first.close
28
- STDOUT.reopen pr.last
29
- pr.last.close
30
-
31
- pe.first.close
32
- STDERR.reopen pe.last
33
- pe.last.close
34
-
35
- STDOUT.sync = STDERR.sync = true
36
-
37
- begin
38
- cmd.call(ps)
39
- rescue Exception => e
40
- begin
41
- Marshal.dump(e, ps.last)
42
- ps.last.flush
43
- rescue Errno::EPIPE
44
- raise e
45
- end
46
- ensure
47
- ps.last.close unless ps.last.closed?
48
- end
49
-
50
- exit!
51
- }
52
- ensure
53
- $VERBOSE = verbose
54
- end
55
-
56
- [ pw.first, pr.last, pe.last, ps.last ].each { |fd| fd.close }
57
-
58
- Open4.propagate_exception cid, ps.first if exception_propagation_at == :init
59
-
60
- pw.last.sync = true
61
-
62
- pi = [ pw.last, pr.first, pe.first ]
63
-
64
- begin
65
- return [cid, *pi] unless b
66
-
67
- begin
68
- b.call(cid, *pi)
69
- ensure
70
- pi.each { |fd| fd.close unless fd.closed? }
71
- end
72
-
73
- Open4.propagate_exception cid, ps.first if exception_propagation_at == :block
74
-
75
- Process.waitpid2(cid).last
76
- ensure
77
- ps.first.close unless ps.first.closed?
78
- end
79
- end
80
- end