switchman 3.1.0 → 3.5.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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +15 -14
  3. data/db/migrate/20180828183945_add_default_shard_index.rb +1 -1
  4. data/db/migrate/20190114212900_add_unique_name_indexes.rb +10 -4
  5. data/lib/switchman/active_record/associations.rb +97 -17
  6. data/lib/switchman/active_record/attribute_methods.rb +72 -43
  7. data/lib/switchman/active_record/base.rb +107 -21
  8. data/lib/switchman/active_record/calculations.rb +37 -33
  9. data/lib/switchman/active_record/connection_pool.rb +21 -2
  10. data/lib/switchman/active_record/database_configurations.rb +12 -7
  11. data/lib/switchman/active_record/finder_methods.rb +1 -1
  12. data/lib/switchman/active_record/log_subscriber.rb +2 -2
  13. data/lib/switchman/active_record/migration.rb +35 -8
  14. data/lib/switchman/active_record/persistence.rb +8 -0
  15. data/lib/switchman/active_record/postgresql_adapter.rb +11 -10
  16. data/lib/switchman/active_record/query_cache.rb +1 -1
  17. data/lib/switchman/active_record/query_methods.rb +172 -132
  18. data/lib/switchman/active_record/relation.rb +21 -11
  19. data/lib/switchman/active_record/spawn_methods.rb +2 -2
  20. data/lib/switchman/active_record/statement_cache.rb +9 -5
  21. data/lib/switchman/active_record/tasks/database_tasks.rb +1 -1
  22. data/lib/switchman/active_record/test_fixtures.rb +19 -16
  23. data/lib/switchman/active_support/cache.rb +4 -1
  24. data/lib/switchman/arel.rb +6 -6
  25. data/lib/switchman/call_super.rb +8 -2
  26. data/lib/switchman/database_server.rb +21 -26
  27. data/lib/switchman/default_shard.rb +3 -3
  28. data/lib/switchman/engine.rb +33 -18
  29. data/lib/switchman/environment.rb +2 -2
  30. data/lib/switchman/errors.rb +13 -0
  31. data/lib/switchman/guard_rail/relation.rb +2 -1
  32. data/lib/switchman/parallel.rb +2 -2
  33. data/lib/switchman/r_spec_helper.rb +10 -10
  34. data/lib/switchman/shard.rb +49 -32
  35. data/lib/switchman/sharded_instrumenter.rb +5 -1
  36. data/lib/switchman/shared_schema_cache.rb +11 -0
  37. data/lib/switchman/test_helper.rb +1 -1
  38. data/lib/switchman/version.rb +1 -1
  39. data/lib/switchman.rb +10 -4
  40. data/lib/tasks/switchman.rake +42 -39
  41. metadata +24 -9
data/lib/switchman.rb CHANGED
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'guard_rail'
4
- require 'zeitwerk'
3
+ require "guard_rail"
4
+ require "zeitwerk"
5
5
 
6
6
  class SwitchmanInflector < Zeitwerk::GemInflector
7
7
  def camelize(basename, abspath)
8
8
  if basename =~ /\Apostgresql_(.*)/
9
- 'PostgreSQL' + super($1, abspath)
9
+ "PostgreSQL" + super($1, abspath)
10
10
  else
11
11
  super
12
12
  end
@@ -32,7 +32,13 @@ module Switchman
32
32
  end
33
33
 
34
34
  def self.foreign_key_check(name, type, limit: nil)
35
- puts "WARNING: All foreign keys need to be 8-byte integers. #{name} looks like a foreign key. If so, please add the option: `:limit => 8`" if name.to_s =~ /_id\z/ && type.to_s == 'integer' && limit.to_i < 8
35
+ return unless name.to_s.end_with?("_id") && type.to_s == "integer" && limit.to_i < 8
36
+
37
+ puts <<~TEXT.squish
38
+ WARNING: All foreign keys need to be 8-byte integers.
39
+ #{name} looks like a foreign key.
40
+ If so, please add the option: `:limit => 8`
41
+ TEXT
36
42
  end
37
43
 
38
44
  class OrderOnMultiShardQuery < RuntimeError; end
@@ -2,10 +2,10 @@
2
2
 
3
3
  # In rails 7.0+ if you have only 1 db in the env it doesn't try to do explicit activation
4
4
  # (and for rails purposes we only have one db per env because each database server is a separate env)
5
- if Rails.version < '7.0'
6
- task_prefix = ::Rake::Task.task_defined?('app:db:migrate') ? 'app:db' : 'db'
7
- ::Rake::Task["#{task_prefix}:migrate"].clear_actions.enhance do
8
- ::ActiveRecord::Tasks::DatabaseTasks.migrate
5
+ if Rails.version < "7.0"
6
+ task_prefix = Rake::Task.task_defined?("app:db:migrate") ? "app:db" : "db"
7
+ Rake::Task["#{task_prefix}:migrate"].clear_actions.enhance do
8
+ ActiveRecord::Tasks::DatabaseTasks.migrate
9
9
  # Ensure this doesn't blow up when running inside the dummy app
10
10
  Rake::Task["#{task_prefix}:_dump"].invoke
11
11
  end
@@ -13,26 +13,27 @@ end
13
13
 
14
14
  module Switchman
15
15
  module Rake
16
- def self.filter_database_servers(&block)
17
- chain = filter_database_servers_chain # use a local variable so that the current chain is closed over in the following lambda
18
- @filter_database_servers_chain = ->(servers) { block.call(servers, chain) }
16
+ def self.filter_database_servers
17
+ # use a local variable so that the current chain is closed over in the following lambda
18
+ chain = filter_database_servers_chain
19
+ @filter_database_servers_chain = ->(servers) { yield(servers, chain) }
19
20
  end
20
21
 
21
22
  def self.scope(base_scope = Shard,
22
- database_server: ENV['DATABASE_SERVER'],
23
- shard: ENV['SHARD'])
23
+ database_server: ENV.fetch("DATABASE_SERVER", nil),
24
+ shard: ENV.fetch("SHARD", nil))
24
25
  servers = DatabaseServer.all
25
26
 
26
27
  if database_server
27
28
  servers = database_server
28
- if servers.first == '-'
29
+ if servers.first == "-"
29
30
  negative = true
30
31
  servers = servers[1..]
31
32
  end
32
- servers = servers.split(',')
33
- open = servers.delete('open')
33
+ servers = servers.split(",")
34
+ open = servers.delete("open")
34
35
 
35
- servers = servers.map { |server| DatabaseServer.find(server) }.compact
36
+ servers = servers.filter_map { |server| DatabaseServer.find(server) }
36
37
  if open
37
38
  open_servers = DatabaseServer.all.select { |server| server.config[:open] }
38
39
  servers.concat(open_servers)
@@ -44,7 +45,7 @@ module Switchman
44
45
 
45
46
  servers = filter_database_servers_chain.call(servers)
46
47
 
47
- scope = base_scope.order(::Arel.sql('database_server_id IS NOT NULL, database_server_id, id'))
48
+ scope = base_scope.order(::Arel.sql("database_server_id IS NOT NULL, database_server_id, id"))
48
49
  if servers != DatabaseServer.all
49
50
  database_server_ids = servers.map(&:id)
50
51
  database_server_ids << nil if servers.include?(Shard.default.database_server)
@@ -57,7 +58,7 @@ module Switchman
57
58
  end
58
59
 
59
60
  def self.options
60
- { parallel: ENV['PARALLEL'].to_i }
61
+ { parallel: ENV["PARALLEL"].to_i }
61
62
  end
62
63
 
63
64
  # classes - an array or proc, to activate as the current shard during the
@@ -69,7 +70,7 @@ module Switchman
69
70
 
70
71
  old_task.enhance do |*task_args|
71
72
  if ::Rails.env.test?
72
- require 'switchman/test_helper'
73
+ require "switchman/test_helper"
73
74
  TestHelper.recreate_persistent_test_shards(dont_create: true)
74
75
  end
75
76
 
@@ -86,7 +87,9 @@ module Switchman
86
87
  nil
87
88
  end
88
89
  rescue => e
89
- warn "Exception from #{e.current_shard.id}: #{e.current_shard.description}:\n#{e.full_message}" if options[:parallel] != 0
90
+ if options[:parallel] != 0
91
+ warn "Exception from #{e.current_shard.id}: #{e.current_shard.description}:\n#{e.full_message}"
92
+ end
90
93
  raise
91
94
  end
92
95
  end
@@ -98,7 +101,7 @@ module Switchman
98
101
  end
99
102
 
100
103
  def self.shard_scope(scope, raw_shard_ids)
101
- raw_shard_ids = raw_shard_ids.split(',')
104
+ raw_shard_ids = raw_shard_ids.split(",")
102
105
 
103
106
  shard_ids = []
104
107
  negative_shard_ids = []
@@ -108,13 +111,13 @@ module Switchman
108
111
 
109
112
  raw_shard_ids.each do |id|
110
113
  case id
111
- when 'default'
114
+ when "default"
112
115
  shard_ids << Shard.default.id
113
- when '-default'
116
+ when "-default"
114
117
  negative_shard_ids << Shard.default.id
115
- when 'primary'
118
+ when "primary"
116
119
  shard_ids.concat(Shard.primary.pluck(:id))
117
- when '-primary'
120
+ when "-primary"
118
121
  negative_shard_ids.concat(Shard.primary.pluck(:id))
119
122
  when /^(-?)(\d+)?\.\.(\.)?(\d+)?$/
120
123
  negative, start, open, finish = $1.present?, $2, $3.present?, $4
@@ -122,8 +125,8 @@ module Switchman
122
125
 
123
126
  range = []
124
127
  range << "id>=#{start}" if start
125
- range << "id<#{'=' unless open}#{finish}" if finish
126
- (negative ? negative_ranges : ranges) << "(#{range.join(' AND ')})"
128
+ range << "id<#{"=" unless open}#{finish}" if finish
129
+ (negative ? negative_ranges : ranges) << "(#{range.join(" AND ")})"
127
130
  when /^-(\d+)$/
128
131
  negative_shard_ids << $1.to_i
129
132
  when /^\d+$/
@@ -151,21 +154,21 @@ module Switchman
151
154
  select = []
152
155
  if index != 1
153
156
  subscope = subscope.offset(per_chunk * (index - 1))
154
- select << 'MIN(id) AS min_id'
157
+ select << "MIN(id) AS min_id"
155
158
  end
156
159
  if index != denominator
157
160
  subscope = subscope.limit(per_chunk)
158
- select << 'MAX(id) AS max_id'
161
+ select << "MAX(id) AS max_id"
159
162
  end
160
163
 
161
- result = Shard.from(subscope).select(select.join(', ')).to_a.first
164
+ result = Shard.from(subscope).select(select.join(", ")).to_a.first
162
165
  range = case index
163
166
  when 1
164
- "id<=#{result['max_id']}"
167
+ "id<=#{result["max_id"]}"
165
168
  when denominator
166
- "id>=#{result['min_id']}"
169
+ "id>=#{result["min_id"]}"
167
170
  else
168
- "(id>=#{result['min_id']} AND id<=#{result['max_id']})"
171
+ "(id>=#{result["min_id"]} AND id<=#{result["max_id"]})"
169
172
  end
170
173
 
171
174
  (numerator.negative? ? negative_ranges : ranges) << range
@@ -186,16 +189,16 @@ module Switchman
186
189
 
187
190
  conditions = []
188
191
  positive_queries = []
189
- positive_queries << ranges.join(' OR ') unless ranges.empty?
192
+ positive_queries << ranges.join(" OR ") unless ranges.empty?
190
193
  unless shard_ids.empty?
191
- positive_queries << 'id IN (?)'
194
+ positive_queries << "id IN (?)"
192
195
  conditions << shard_ids
193
196
  end
194
- positive_query = positive_queries.join(' OR ')
197
+ positive_query = positive_queries.join(" OR ")
195
198
  scope = scope.where(positive_query, *conditions) unless positive_queries.empty?
196
199
 
197
- scope = scope.where("NOT (#{negative_ranges.join(' OR')})") unless negative_ranges.empty?
198
- scope = scope.where('id NOT IN (?)', negative_shard_ids) unless negative_shard_ids.empty?
200
+ scope = scope.where("NOT (#{negative_ranges.join(" OR")})") unless negative_ranges.empty?
201
+ scope = scope.where("id NOT IN (?)", negative_shard_ids) unless negative_shard_ids.empty?
199
202
  scope
200
203
  end
201
204
 
@@ -208,19 +211,19 @@ module Switchman
208
211
  module PostgreSQLDatabaseTasks
209
212
  def structure_dump(filename, extra_flags = nil)
210
213
  set_psql_env
211
- args = ['--schema-only', '--no-privileges', '--no-owner', '--file', filename]
214
+ args = ["--schema-only", "--no-privileges", "--no-owner", "--file", filename]
212
215
  args.concat(Array(extra_flags)) if extra_flags
213
216
  shard = Shard.current.name
214
217
  serialized_search_path = shard
215
218
  args << "--schema=#{Shellwords.escape(shard)}"
216
219
 
217
220
  ignore_tables = ::ActiveRecord::SchemaDumper.ignore_tables
218
- args += ignore_tables.flat_map { |table| ['-T', table] } if ignore_tables.any?
221
+ args += ignore_tables.flat_map { |table| ["-T", table] } if ignore_tables.any?
219
222
 
220
223
  args << db_config.database
221
- run_cmd('pg_dump', args, 'dumping')
224
+ run_cmd("pg_dump", args, "dumping")
222
225
  remove_sql_header_comments(filename)
223
- File.open(filename, 'a') { |f| f << "SET search_path TO #{serialized_search_path};\n\n" }
226
+ File.open(filename, "a") { |f| f << "SET search_path TO #{serialized_search_path};\n\n" }
224
227
  end
225
228
  end
226
229
  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: 3.1.0
4
+ version: 3.5.0
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: 2022-06-02 00:00:00.000000000 Z
13
+ date: 2023-05-02 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activerecord
@@ -86,14 +86,14 @@ dependencies:
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: 2.3.0
89
+ version: '2.3'
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.3.0
96
+ version: '2.3'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: byebug
99
99
  requirement: !ruby/object:Gem::Requirement
@@ -170,14 +170,14 @@ dependencies:
170
170
  requirements:
171
171
  - - "~>"
172
172
  - !ruby/object:Gem::Version
173
- version: '4.0'
173
+ version: '6.0'
174
174
  type: :development
175
175
  prerelease: false
176
176
  version_requirements: !ruby/object:Gem::Requirement
177
177
  requirements:
178
178
  - - "~>"
179
179
  - !ruby/object:Gem::Version
180
- version: '4.0'
180
+ version: '6.0'
181
181
  - !ruby/object:Gem::Dependency
182
182
  name: rubocop
183
183
  requirement: !ruby/object:Gem::Requirement
@@ -192,6 +192,20 @@ dependencies:
192
192
  - - "~>"
193
193
  - !ruby/object:Gem::Version
194
194
  version: '1.10'
195
+ - !ruby/object:Gem::Dependency
196
+ name: rubocop-inst
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - "~>"
200
+ - !ruby/object:Gem::Version
201
+ version: '1'
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - "~>"
207
+ - !ruby/object:Gem::Version
208
+ version: '1'
195
209
  - !ruby/object:Gem::Dependency
196
210
  name: rubocop-rake
197
211
  requirement: !ruby/object:Gem::Requirement
@@ -290,6 +304,7 @@ files:
290
304
  - lib/switchman/rails.rb
291
305
  - lib/switchman/shard.rb
292
306
  - lib/switchman/sharded_instrumenter.rb
307
+ - lib/switchman/shared_schema_cache.rb
293
308
  - lib/switchman/standard_error.rb
294
309
  - lib/switchman/test_helper.rb
295
310
  - lib/switchman/unsharded_record.rb
@@ -300,7 +315,7 @@ licenses:
300
315
  - MIT
301
316
  metadata:
302
317
  rubygems_mfa_required: 'true'
303
- post_install_message:
318
+ post_install_message:
304
319
  rdoc_options: []
305
320
  require_paths:
306
321
  - lib
@@ -316,7 +331,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
316
331
  version: '0'
317
332
  requirements: []
318
333
  rubygems_version: 3.1.6
319
- signing_key:
334
+ signing_key:
320
335
  specification_version: 4
321
336
  summary: Rails sharding magic
322
337
  test_files: []