switchman 4.3.5 → 4.3.6
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 +4 -4
- data/db/migrate/20130328212039_create_switchman_shards.rb +1 -1
- data/db/migrate/20180828183945_add_default_shard_index.rb +2 -2
- data/db/migrate/20180828192111_add_timestamps_to_shards.rb +1 -1
- data/lib/switchman/active_record/abstract_adapter.rb +2 -2
- data/lib/switchman/active_record/attribute_methods.rb +5 -0
- data/lib/switchman/active_record/base.rb +2 -2
- data/lib/switchman/active_record/calculations.rb +1 -1
- data/lib/switchman/active_record/finder_methods.rb +1 -1
- data/lib/switchman/active_record/pending_migration_connection.rb +1 -7
- data/lib/switchman/active_record/postgresql_adapter.rb +1 -3
- data/lib/switchman/active_record/query_methods.rb +1 -3
- data/lib/switchman/active_record/test_fixtures.rb +2 -2
- data/lib/switchman/active_support/cache.rb +1 -2
- data/lib/switchman/database_server.rb +1 -1
- data/lib/switchman/engine.rb +15 -13
- data/lib/switchman/parallel.rb +1 -3
- data/lib/switchman/r_spec_helper.rb +2 -1
- data/lib/switchman/shard.rb +6 -10
- data/lib/switchman/unsharded_record.rb +1 -1
- data/lib/switchman/version.rb +1 -1
- data/lib/switchman.rb +1 -1
- data/lib/tasks/switchman.rake +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2006f24bb61000cf22294395b7597c5551a56600b7c1b1fea0923f3d94e498e3
|
|
4
|
+
data.tar.gz: 30818e17d6547d86d46936e01c2df78118bbf992667cd1614c69bab9e173211c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 110c91ddc723bde6ec5cbacbb992a67d87da0880fca81e22ff1b3c8d7852b0a53bc3024501c9eaae3c7a5f355615a5f6d880faf4fd5ace003adc148bcb14d3a8
|
|
7
|
+
data.tar.gz: '08a4458dcd9fa616e9079bd98f3609c42fa397941426c69725d517018c19b3e88dd952702c9bd7870112e8210fb85d94c8d6f706199aac1554c371e1f46b6f29'
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
class CreateSwitchmanShards < ActiveRecord::Migration[4.2]
|
|
4
4
|
def change
|
|
5
|
-
create_table :switchman_shards do |t|
|
|
5
|
+
create_table :switchman_shards do |t| # rubocop:disable Rails/CreateTableWithTimestamps -- added in a subsequent migration
|
|
6
6
|
t.string :name
|
|
7
7
|
t.string :database_server_id
|
|
8
8
|
t.boolean :default, default: false, null: false
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
class AddDefaultShardIndex < ActiveRecord::Migration[4.2]
|
|
4
4
|
def change
|
|
5
|
-
Switchman::Shard.where(default: nil).update_all(default: false) if Switchman::Shard.current.default?
|
|
6
|
-
change_column_default :switchman_shards, :default, false
|
|
5
|
+
Switchman::Shard.where(default: nil).update_all(default: false) if Switchman::Shard.current.default? # rubocop:disable Rails/SkipsModelValidations
|
|
6
|
+
change_column_default :switchman_shards, :default, from: nil, to: false
|
|
7
7
|
change_column_null :switchman_shards, :default, false
|
|
8
8
|
options = if connection.adapter_name == "PostgreSQL"
|
|
9
9
|
{ unique: true, where: '"default"' }
|
|
@@ -6,7 +6,7 @@ class AddTimestampsToShards < ActiveRecord::Migration[4.2]
|
|
|
6
6
|
def change
|
|
7
7
|
add_timestamps :switchman_shards, null: true, if_not_exists: true
|
|
8
8
|
now = Time.now.utc
|
|
9
|
-
Switchman::Shard.update_all(updated_at: now, created_at: now) if Switchman::Shard.current.default?
|
|
9
|
+
Switchman::Shard.update_all(updated_at: now, created_at: now) if Switchman::Shard.current.default? # rubocop:disable Rails/SkipsModelValidations
|
|
10
10
|
change_column_null :switchman_shards, :updated_at, false
|
|
11
11
|
change_column_null :switchman_shards, :created_at, false
|
|
12
12
|
|
|
@@ -22,7 +22,7 @@ module Switchman
|
|
|
22
22
|
|
|
23
23
|
@instrumenter = Switchman::ShardedInstrumenter.new(@instrumenter, self) if ::Rails.version < "8.0"
|
|
24
24
|
|
|
25
|
-
@last_query_at = Time.now
|
|
25
|
+
@last_query_at = Time.zone.now
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
if ::Rails.version >= "8.0"
|
|
@@ -40,7 +40,7 @@ module Switchman
|
|
|
40
40
|
def log(...)
|
|
41
41
|
super
|
|
42
42
|
ensure
|
|
43
|
-
@last_query_at = Time.now
|
|
43
|
+
@last_query_at = Time.zone.now
|
|
44
44
|
end
|
|
45
45
|
end
|
|
46
46
|
end
|
|
@@ -27,6 +27,11 @@ module Switchman
|
|
|
27
27
|
|
|
28
28
|
def define_attribute_methods
|
|
29
29
|
result = super
|
|
30
|
+
# ActiveRecord::Core#init_internals calls this for every record it instantiates,
|
|
31
|
+
# and AR returns false once the methods have already been generated. Our class_evals
|
|
32
|
+
# below are just as redundant in that case, so don't pay for them per-record.
|
|
33
|
+
return result unless result
|
|
34
|
+
|
|
30
35
|
# ensure that we're using the sharded attribute method
|
|
31
36
|
# and not the silly one in AR::AttributeMethods::PrimaryKey
|
|
32
37
|
return result unless sharded_column?(@primary_key)
|
|
@@ -190,7 +190,7 @@ module Switchman
|
|
|
190
190
|
end
|
|
191
191
|
end
|
|
192
192
|
target_shard.activate do
|
|
193
|
-
self.class.upsert_all([shadow_attrs], unique_by: self.class.primary_key)
|
|
193
|
+
self.class.upsert_all([shadow_attrs], unique_by: self.class.primary_key) # rubocop:disable Rails/SkipsModelValidations
|
|
194
194
|
end
|
|
195
195
|
end
|
|
196
196
|
|
|
@@ -205,7 +205,7 @@ module Switchman
|
|
|
205
205
|
Array(target_shards).each do |target_shard|
|
|
206
206
|
next if target_shard == shard
|
|
207
207
|
|
|
208
|
-
|
|
208
|
+
self.class.shard(target_shard).where(id: global_id).delete_all
|
|
209
209
|
end
|
|
210
210
|
end
|
|
211
211
|
|
|
@@ -83,7 +83,7 @@ module Switchman
|
|
|
83
83
|
if opts[:association]
|
|
84
84
|
key_ids = calculated_data.collect { |row| row[opts[:group_aliases].first] }
|
|
85
85
|
key_records = opts[:association].klass.base_class.where(id: key_ids)
|
|
86
|
-
key_records = key_records.
|
|
86
|
+
key_records = key_records.index_by { |r| Shard.relative_id_for(r, shard, target_shard) }
|
|
87
87
|
end
|
|
88
88
|
|
|
89
89
|
calculated_data.map do |row|
|
|
@@ -46,7 +46,7 @@ module Switchman
|
|
|
46
46
|
return false if @none
|
|
47
47
|
|
|
48
48
|
if Base === conditions
|
|
49
|
-
raise ArgumentError,
|
|
49
|
+
raise ArgumentError, <<~TEXT.squish
|
|
50
50
|
You are passing an instance of ActiveRecord::Base to `exists?`.
|
|
51
51
|
Please pass the id of the object by calling `.id`.
|
|
52
52
|
TEXT
|
|
@@ -4,13 +4,7 @@ module Switchman
|
|
|
4
4
|
module ActiveRecord
|
|
5
5
|
module PendingMigrationConnection
|
|
6
6
|
module ClassMethods
|
|
7
|
-
|
|
8
|
-
::ActiveRecord::Base.current_role
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def current_switchman_shard
|
|
12
|
-
::ActiveRecord::Base.current_switchman_shard
|
|
13
|
-
end
|
|
7
|
+
delegate :current_role, :current_switchman_shard, to: :"::ActiveRecord::Base"
|
|
14
8
|
end
|
|
15
9
|
end
|
|
16
10
|
end
|
|
@@ -94,9 +94,7 @@ module Switchman
|
|
|
94
94
|
end
|
|
95
95
|
end
|
|
96
96
|
|
|
97
|
-
|
|
98
|
-
self.class.quote_local_table_name(name)
|
|
99
|
-
end
|
|
97
|
+
delegate :quote_local_table_name, to: :class
|
|
100
98
|
|
|
101
99
|
def quote_table_name(name)
|
|
102
100
|
self.class.quote_table_name(name, shard: @use_local_table_name ? nil : shard)
|
|
@@ -5,9 +5,7 @@ module Switchman
|
|
|
5
5
|
module QueryMethods
|
|
6
6
|
# Use this class to prevent a value from getting transposed across shards
|
|
7
7
|
class NonTransposingValue < SimpleDelegator
|
|
8
|
-
|
|
9
|
-
__getobj__.class
|
|
10
|
-
end
|
|
8
|
+
delegate :class, to: :__getobj__
|
|
11
9
|
|
|
12
10
|
def is_a?(other)
|
|
13
11
|
return true if other == NonTransposingValue
|
|
@@ -18,7 +18,7 @@ module Switchman
|
|
|
18
18
|
spec_name = (payload[:connection_name] if payload.key?(:connection_name))
|
|
19
19
|
shard = payload[:shard] if payload.key?(:shard)
|
|
20
20
|
|
|
21
|
-
if spec_name &&
|
|
21
|
+
if spec_name && FORBIDDEN_DB_ENVS.exclude?(shard)
|
|
22
22
|
begin
|
|
23
23
|
connection = ::ActiveRecord::Base.connection_handler.retrieve_connection(spec_name, shard:)
|
|
24
24
|
connection.connect! # eagerly validate the connection
|
|
@@ -69,7 +69,7 @@ module Switchman
|
|
|
69
69
|
shard = payload[:shard] if payload.key?(:shard)
|
|
70
70
|
|
|
71
71
|
# INST: filter by FORBIDDEN_DB_ENVS
|
|
72
|
-
if connection_name &&
|
|
72
|
+
if connection_name && FORBIDDEN_DB_ENVS.exclude?(shard)
|
|
73
73
|
pool = ::ActiveRecord::Base.connection_handler.retrieve_connection_pool(connection_name, shard:)
|
|
74
74
|
if pool
|
|
75
75
|
setup_shared_connection_pool
|
|
@@ -24,12 +24,11 @@ module Switchman
|
|
|
24
24
|
store = super
|
|
25
25
|
# must use the string name, otherwise it will try to auto-load the constant
|
|
26
26
|
# and we don't want to require redis in this file (since it's not a hard dependency)
|
|
27
|
-
# rubocop:disable Style/ClassEqualityComparison
|
|
27
|
+
# rubocop:disable-next Style/ClassEqualityComparison
|
|
28
28
|
if store.class.name == "ActiveSupport::Cache::RedisCacheStore" &&
|
|
29
29
|
!(::ActiveSupport::Cache::RedisCacheStore <= RedisCacheStore)
|
|
30
30
|
::ActiveSupport::Cache::RedisCacheStore.prepend(RedisCacheStore)
|
|
31
31
|
end
|
|
32
|
-
# rubocop:enable Style/ClassEqualityComparison
|
|
33
32
|
store.options[:namespace] ||= -> { Shard.current.default? ? nil : "shard_#{Shard.current.id}" }
|
|
34
33
|
store
|
|
35
34
|
end
|
|
@@ -68,7 +68,7 @@ module Switchman
|
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
def database_servers
|
|
71
|
-
if
|
|
71
|
+
if @database_servers.blank?
|
|
72
72
|
@database_servers = {}.with_indifferent_access
|
|
73
73
|
roles = []
|
|
74
74
|
::ActiveRecord::Base.configurations.configurations.each do |config|
|
data/lib/switchman/engine.rb
CHANGED
|
@@ -48,15 +48,6 @@ module Switchman
|
|
|
48
48
|
::ActiveRecord::ConnectionAdapters::ConnectionHandler.prepend(ActiveRecord::ConnectionHandler)
|
|
49
49
|
::ActiveRecord::ConnectionAdapters::ConnectionPool.prepend(ActiveRecord::ConnectionPool)
|
|
50
50
|
::ActiveRecord::ConnectionAdapters::AbstractAdapter.prepend(ActiveRecord::QueryCache)
|
|
51
|
-
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(ActiveRecord::PostgreSQLAdapter)
|
|
52
|
-
# https://github.com/rails/rails/commit/0016280f4fde55d96738887093dc333aae0d107b
|
|
53
|
-
if ::Rails.version < "7.2"
|
|
54
|
-
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(ActiveRecord::PostgreSQLAdapter::ClassMethods)
|
|
55
|
-
else
|
|
56
|
-
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.singleton_class.prepend(
|
|
57
|
-
ActiveRecord::PostgreSQLAdapter::ClassMethods
|
|
58
|
-
)
|
|
59
|
-
end
|
|
60
51
|
|
|
61
52
|
::ActiveRecord::DatabaseConfigurations.prepend(ActiveRecord::DatabaseConfigurations)
|
|
62
53
|
::ActiveRecord::DatabaseConfigurations::DatabaseConfig.prepend(
|
|
@@ -93,8 +84,6 @@ module Switchman
|
|
|
93
84
|
|
|
94
85
|
::ActiveRecord::Tasks::DatabaseTasks.singleton_class.prepend(ActiveRecord::Tasks::DatabaseTasks)
|
|
95
86
|
|
|
96
|
-
::ActiveRecord::TestFixtures.prepend(ActiveRecord::TestFixtures)
|
|
97
|
-
|
|
98
87
|
::ActiveRecord::TypeCaster::Map.include(ActiveRecord::TypeCaster::Map)
|
|
99
88
|
::ActiveRecord::TypeCaster::Connection.include(ActiveRecord::TypeCaster::Connection)
|
|
100
89
|
|
|
@@ -107,6 +96,19 @@ module Switchman
|
|
|
107
96
|
|
|
108
97
|
::ActiveRecord::ConnectionAdapters::TableDefinition.prepend(ActiveRecord::TableDefinition)
|
|
109
98
|
end
|
|
99
|
+
|
|
100
|
+
::ActiveSupport.on_load(:active_record_postgresqladapter) do
|
|
101
|
+
prepend ActiveRecord::PostgreSQLAdapter
|
|
102
|
+
|
|
103
|
+
# https://github.com/rails/rails/commit/0016280f4fde55d96738887093dc333aae0d107b
|
|
104
|
+
if ::Rails.version < "7.2"
|
|
105
|
+
prepend(ActiveRecord::PostgreSQLAdapter::ClassMethods)
|
|
106
|
+
else
|
|
107
|
+
singleton_class.prepend(ActiveRecord::PostgreSQLAdapter::ClassMethods)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
::ActiveSupport.on_load(:active_record_fixtures) { prepend ActiveRecord::TestFixtures }
|
|
110
112
|
# Ensure that ActiveRecord::Base is always loaded before any app-level
|
|
111
113
|
# initializers can go try to load Switchman::Shard or we get a loop
|
|
112
114
|
::ActiveRecord::Base
|
|
@@ -126,7 +128,7 @@ module Switchman
|
|
|
126
128
|
# initialize_cache initializer, but for each value in the map, rather
|
|
127
129
|
# than just Rails.cache. if config.cache_store is a flat value, uses it
|
|
128
130
|
# to fill just the Rails.env entry in the cache map.
|
|
129
|
-
|
|
131
|
+
if Switchman.config[:cache_map].blank?
|
|
130
132
|
cache_store_config = ::Rails.configuration.cache_store
|
|
131
133
|
cache_store_config = { ::Rails.env => cache_store_config } unless cache_store_config.is_a?(Hash)
|
|
132
134
|
|
|
@@ -158,7 +160,7 @@ module Switchman
|
|
|
158
160
|
::Rails.singleton_class.prepend(Rails::ClassMethods)
|
|
159
161
|
|
|
160
162
|
::ActiveSupport.on_load(:action_controller) do
|
|
161
|
-
|
|
163
|
+
include ActionController::Caching
|
|
162
164
|
end
|
|
163
165
|
end
|
|
164
166
|
end
|
data/lib/switchman/parallel.rb
CHANGED
|
@@ -17,6 +17,7 @@ module Switchman
|
|
|
17
17
|
klass.parent_groups.any? { |group| group.included_modules.include?(self) }
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
+
# rubocop:disable-next Rails/Output
|
|
20
21
|
def self.included(klass)
|
|
21
22
|
# our before handlers have already been configured from a parent group; don't add them again
|
|
22
23
|
parent_group = klass.parent_groups[1]
|
|
@@ -85,7 +86,7 @@ module Switchman
|
|
|
85
86
|
@@shard2.destroy
|
|
86
87
|
end
|
|
87
88
|
@@shard2.database_server.destroy
|
|
88
|
-
exit status if status
|
|
89
|
+
exit status if status # rubocop:disable Rails/Exit
|
|
89
90
|
end
|
|
90
91
|
end
|
|
91
92
|
|
data/lib/switchman/shard.rb
CHANGED
|
@@ -5,10 +5,8 @@ module Switchman
|
|
|
5
5
|
# ten trillion possible ids per shard. yup.
|
|
6
6
|
IDS_PER_SHARD = 10_000_000_000_000
|
|
7
7
|
|
|
8
|
-
# rubocop:disable Style/SymbolProc -- transforming to a lambda produces "no receiver given"
|
|
9
8
|
# only allow one default
|
|
10
9
|
validates_uniqueness_of :default, if: ->(s) { s.default? }
|
|
11
|
-
# rubocop:enable Style/SymbolProc
|
|
12
10
|
|
|
13
11
|
after_save :clear_cache
|
|
14
12
|
after_destroy :clear_cache
|
|
@@ -79,7 +77,7 @@ module Switchman
|
|
|
79
77
|
|
|
80
78
|
# Now find the actual record, if it exists
|
|
81
79
|
@default = begin
|
|
82
|
-
find_cached("default_shard") { Shard.
|
|
80
|
+
find_cached("default_shard") { Shard.find_by(default: true) } || default
|
|
83
81
|
rescue
|
|
84
82
|
default
|
|
85
83
|
end
|
|
@@ -215,8 +213,8 @@ module Switchman
|
|
|
215
213
|
# nothing to do
|
|
216
214
|
return if database_servers.none?
|
|
217
215
|
|
|
218
|
-
scopes = database_servers.
|
|
219
|
-
|
|
216
|
+
scopes = database_servers.index_with do |server|
|
|
217
|
+
scope.merge(server.shards)
|
|
220
218
|
end
|
|
221
219
|
else
|
|
222
220
|
scopes = scope.group_by(&:database_server)
|
|
@@ -665,9 +663,7 @@ module Switchman
|
|
|
665
663
|
end
|
|
666
664
|
|
|
667
665
|
# skip global_id.hash
|
|
668
|
-
|
|
669
|
-
id.hash
|
|
670
|
-
end
|
|
666
|
+
delegate :hash, to: :id
|
|
671
667
|
|
|
672
668
|
def destroy
|
|
673
669
|
raise("Cannot destroy the default shard") if default?
|
|
@@ -696,8 +692,8 @@ module Switchman
|
|
|
696
692
|
if classes.empty?
|
|
697
693
|
{ ::ActiveRecord::Base => self }
|
|
698
694
|
else
|
|
699
|
-
classes.
|
|
700
|
-
|
|
695
|
+
classes.index_with do
|
|
696
|
+
self
|
|
701
697
|
end
|
|
702
698
|
end
|
|
703
699
|
end
|
data/lib/switchman/version.rb
CHANGED
data/lib/switchman.rb
CHANGED
|
@@ -39,7 +39,7 @@ module Switchman
|
|
|
39
39
|
def foreign_key_check(name, type, limit: nil)
|
|
40
40
|
return unless name.to_s.end_with?("_id") && type.to_s == "integer" && limit.to_i < 8
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
warn <<~TEXT.squish
|
|
43
43
|
WARNING: All foreign keys need to be 8-byte integers.
|
|
44
44
|
#{name} looks like a foreign key.
|
|
45
45
|
If so, please add the option: `:limit => 8`
|
data/lib/tasks/switchman.rake
CHANGED
|
@@ -243,7 +243,7 @@ module Switchman
|
|
|
243
243
|
scope = scope.where(positive_query, *conditions) unless positive_queries.empty?
|
|
244
244
|
|
|
245
245
|
scope = scope.where("NOT (#{negative_ranges.join(" OR")})") unless negative_ranges.empty?
|
|
246
|
-
scope = scope.where(
|
|
246
|
+
scope = scope.where.not(id: negative_shard_ids) unless negative_shard_ids.empty?
|
|
247
247
|
scope
|
|
248
248
|
end
|
|
249
249
|
|
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: 4.3.
|
|
4
|
+
version: 4.3.6
|
|
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: 2026-09-
|
|
13
|
+
date: 2026-09-10 00:00:00.000000000 Z
|
|
14
14
|
dependencies:
|
|
15
15
|
- !ruby/object:Gem::Dependency
|
|
16
16
|
name: activerecord
|