switchman 1.15.0 → 1.16.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.
- checksums.yaml +4 -4
- data/app/models/switchman/shard.rb +39 -17
- data/lib/switchman/active_record/abstract_adapter.rb +0 -4
- data/lib/switchman/active_record/base.rb +9 -0
- data/lib/switchman/active_record/connection_handler.rb +0 -8
- data/lib/switchman/active_record/connection_pool.rb +0 -12
- data/lib/switchman/active_record/migration.rb +9 -0
- data/lib/switchman/active_record/postgresql_adapter.rb +9 -20
- data/lib/switchman/active_record/statement_cache.rb +2 -15
- data/lib/switchman/engine.rb +1 -0
- data/lib/switchman/version.rb +1 -1
- data/lib/tasks/switchman.rake +3 -10
- metadata +19 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 70787591a2f55963ba4b74b816cb11428d1d764e87d3635309d98b423ed842e1
|
|
4
|
+
data.tar.gz: 5516037d6c39f1215eac8764512d97125dbeaf2cb64aeed234f5a4d55ce18650
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bad00cb3a194c18f792c214caa3f47f90ac53b7186ea458775b9562e1bf2b8fa898e6317ca4514722ce3a9e8652061101e15f657556f2c95344c6ecd5284bb34
|
|
7
|
+
data.tar.gz: '0697c43ffd9934ddfb9d0640295024e1035297ac7f14e9a3018a80f0170e427904924d4efa42892a5cf2f223aa75490c9ccd0da500c7040c4c53da4b04f8d9a7'
|
|
@@ -425,6 +425,19 @@ module Switchman
|
|
|
425
425
|
end
|
|
426
426
|
end
|
|
427
427
|
|
|
428
|
+
# it's tedious to hold onto this same
|
|
429
|
+
# kind of sign state and transform the
|
|
430
|
+
# result in multiple places, so
|
|
431
|
+
# here we can operate on the absolute value
|
|
432
|
+
# in a provided block and trust the sign will
|
|
433
|
+
# stay as provided. This assumes no consumer
|
|
434
|
+
# will return a nil value from the block.
|
|
435
|
+
def signed_id_operation(input_id)
|
|
436
|
+
sign = input_id < 0 ? -1 : 1
|
|
437
|
+
output = yield input_id.abs
|
|
438
|
+
output * sign
|
|
439
|
+
end
|
|
440
|
+
|
|
428
441
|
# converts an AR object, integral id, string id, or string short-global-id to a
|
|
429
442
|
# integral id. nil if it can't be interpreted
|
|
430
443
|
def integral_id_for(any_id)
|
|
@@ -437,12 +450,13 @@ module Switchman
|
|
|
437
450
|
case any_id
|
|
438
451
|
when ::ActiveRecord::Base
|
|
439
452
|
any_id.id
|
|
440
|
-
when /^(\d+)~(
|
|
453
|
+
when /^(\d+)~(-?\d+)$/
|
|
441
454
|
local_id = $2.to_i
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
455
|
+
signed_id_operation(local_id) do |id|
|
|
456
|
+
return nil if id > IDS_PER_SHARD
|
|
457
|
+
$1.to_i * IDS_PER_SHARD + id
|
|
458
|
+
end
|
|
459
|
+
when Integer, /^-?\d+$/
|
|
446
460
|
any_id.to_i
|
|
447
461
|
else
|
|
448
462
|
nil
|
|
@@ -457,13 +471,17 @@ module Switchman
|
|
|
457
471
|
def local_id_for(any_id)
|
|
458
472
|
id = integral_id_for(any_id)
|
|
459
473
|
return NIL_NIL_ID unless id
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
474
|
+
return_shard = nil
|
|
475
|
+
local_id = signed_id_operation(id) do |abs_id|
|
|
476
|
+
if abs_id < IDS_PER_SHARD
|
|
477
|
+
abs_id
|
|
478
|
+
elsif return_shard = lookup(abs_id / IDS_PER_SHARD)
|
|
479
|
+
abs_id % IDS_PER_SHARD
|
|
480
|
+
else
|
|
481
|
+
return NIL_NIL_ID
|
|
482
|
+
end
|
|
466
483
|
end
|
|
484
|
+
[local_id, return_shard]
|
|
467
485
|
end
|
|
468
486
|
|
|
469
487
|
# takes an id-ish, and returns an integral id relative to
|
|
@@ -494,11 +512,13 @@ module Switchman
|
|
|
494
512
|
def global_id_for(any_id, source_shard = nil)
|
|
495
513
|
id = integral_id_for(any_id)
|
|
496
514
|
return any_id unless id
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
515
|
+
signed_id_operation(id) do |abs_id|
|
|
516
|
+
if abs_id >= IDS_PER_SHARD
|
|
517
|
+
abs_id
|
|
518
|
+
else
|
|
519
|
+
source_shard ||= Shard.current
|
|
520
|
+
source_shard.global_id_for(abs_id)
|
|
521
|
+
end
|
|
502
522
|
end
|
|
503
523
|
end
|
|
504
524
|
|
|
@@ -671,7 +691,9 @@ module Switchman
|
|
|
671
691
|
# takes an id local to this shard, and returns a global id
|
|
672
692
|
def global_id_for(local_id)
|
|
673
693
|
return nil unless local_id
|
|
674
|
-
|
|
694
|
+
self.class.signed_id_operation(local_id) do |abs_id|
|
|
695
|
+
abs_id + self.id * IDS_PER_SHARD
|
|
696
|
+
end
|
|
675
697
|
end
|
|
676
698
|
|
|
677
699
|
# skip global_id.hash
|
|
@@ -155,6 +155,15 @@ module Switchman
|
|
|
155
155
|
self.class.connection.quote(id)
|
|
156
156
|
end
|
|
157
157
|
|
|
158
|
+
def update_columns(*)
|
|
159
|
+
db = Shard.current(self.class.shard_category).database_server
|
|
160
|
+
if ::Shackles.environment != db.shackles_environment
|
|
161
|
+
return db.unshackle { super }
|
|
162
|
+
else
|
|
163
|
+
super
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
158
167
|
protected
|
|
159
168
|
|
|
160
169
|
# see also AttributeMethods#shard_category_code_for_reflection
|
|
@@ -4,8 +4,6 @@ module Switchman
|
|
|
4
4
|
module ActiveRecord
|
|
5
5
|
module ConnectionHandler
|
|
6
6
|
def self.make_sharing_automagic(config, shard = Shard.current)
|
|
7
|
-
key = config[:adapter] == 'postgresql' ? :schema_search_path : :database
|
|
8
|
-
|
|
9
7
|
# only load the shard name from the db if we have to
|
|
10
8
|
if !config[:shard_name]
|
|
11
9
|
# we may not be able to connect to this shard yet, cause it might be an empty database server
|
|
@@ -15,12 +13,6 @@ module Switchman
|
|
|
15
13
|
|
|
16
14
|
config[:shard_name] ||= shard_name
|
|
17
15
|
end
|
|
18
|
-
|
|
19
|
-
if !config[key] || config[key] == shard_name
|
|
20
|
-
# this may truncate the schema_search_path if it was not specified in database.yml
|
|
21
|
-
# but that's what our old behavior was anyway, so I guess it's okay
|
|
22
|
-
config[key] = '%{shard_name}'
|
|
23
|
-
end
|
|
24
16
|
end
|
|
25
17
|
|
|
26
18
|
def establish_connection(spec)
|
|
@@ -84,18 +84,6 @@ module Switchman
|
|
|
84
84
|
end
|
|
85
85
|
|
|
86
86
|
spec.config[:shard_name] = self.shard.name
|
|
87
|
-
case conn.adapter_name
|
|
88
|
-
when 'MySQL', 'Mysql2'
|
|
89
|
-
conn.execute("USE #{spec.config[:database]}")
|
|
90
|
-
when 'PostgreSQL'
|
|
91
|
-
if conn.schema_search_path != spec.config[:schema_search_path]
|
|
92
|
-
conn.schema_search_path = spec.config[:schema_search_path]
|
|
93
|
-
end
|
|
94
|
-
when 'SQLite'
|
|
95
|
-
# This is an artifact of the adapter modifying the path to be an absolute path when it is instantiated; just let it slide
|
|
96
|
-
else
|
|
97
|
-
raise("Cannot switch databases on same DatabaseServer with adapter type: #{conn.adapter_name}. Limit one Shard per DatabaseServer.")
|
|
98
|
-
end
|
|
99
87
|
conn.shard = shard
|
|
100
88
|
end
|
|
101
89
|
|
|
@@ -33,5 +33,14 @@ module Switchman
|
|
|
33
33
|
::ActiveRecord::Migrator::MIGRATOR_SALT * shard_name_hash
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
|
+
|
|
37
|
+
module MigrationContext
|
|
38
|
+
def migrations
|
|
39
|
+
return @migrations if instance_variable_defined?(:@migrations)
|
|
40
|
+
migrations_cache = Thread.current[:migrations_cache] ||= {}
|
|
41
|
+
key = Digest::MD5.hexdigest(migration_files.sort.join(','))
|
|
42
|
+
@migrations = migrations_cache[key] ||= super
|
|
43
|
+
end
|
|
44
|
+
end
|
|
36
45
|
end
|
|
37
46
|
end
|
|
@@ -38,23 +38,17 @@ module Switchman
|
|
|
38
38
|
select_values("SELECT * FROM unnest(current_schemas(false))")
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
def use_qualified_names?
|
|
42
|
-
@config[:use_qualified_names]
|
|
43
|
-
end
|
|
44
|
-
|
|
45
41
|
def tables(name = nil)
|
|
46
|
-
schema = shard.name if use_qualified_names?
|
|
47
|
-
|
|
48
42
|
query(<<-SQL, 'SCHEMA').map { |row| row[0] }
|
|
49
43
|
SELECT tablename
|
|
50
44
|
FROM pg_tables
|
|
51
|
-
WHERE schemaname =
|
|
45
|
+
WHERE schemaname = '#{shard.name}'
|
|
52
46
|
SQL
|
|
53
47
|
end
|
|
54
48
|
|
|
55
49
|
def extract_schema_qualified_name(string)
|
|
56
50
|
name = ::ActiveRecord::ConnectionAdapters::PostgreSQL::Utils.extract_schema_qualified_name(string.to_s)
|
|
57
|
-
if string && !name.schema
|
|
51
|
+
if string && !name.schema
|
|
58
52
|
name.instance_variable_set(:@schema, shard.name)
|
|
59
53
|
end
|
|
60
54
|
[name.schema, name.identifier]
|
|
@@ -63,7 +57,7 @@ module Switchman
|
|
|
63
57
|
def view_exists?(name)
|
|
64
58
|
name = ::ActiveRecord::ConnectionAdapters::PostgreSQL::Utils.extract_schema_qualified_name(name.to_s)
|
|
65
59
|
return false unless name.identifier
|
|
66
|
-
if !name.schema
|
|
60
|
+
if !name.schema
|
|
67
61
|
name.instance_variable_set(:@schema, shard.name)
|
|
68
62
|
end
|
|
69
63
|
|
|
@@ -73,13 +67,11 @@ module Switchman
|
|
|
73
67
|
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
74
68
|
WHERE c.relkind IN ('v','m') -- (v)iew, (m)aterialized view
|
|
75
69
|
AND c.relname = '#{name.identifier}'
|
|
76
|
-
AND n.nspname = #{
|
|
70
|
+
AND n.nspname = '#{shard.name}'
|
|
77
71
|
SQL
|
|
78
72
|
end
|
|
79
73
|
|
|
80
74
|
def indexes(table_name)
|
|
81
|
-
schema = shard.name if use_qualified_names?
|
|
82
|
-
|
|
83
75
|
result = query(<<-SQL, 'SCHEMA')
|
|
84
76
|
SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
|
|
85
77
|
FROM pg_class t
|
|
@@ -88,7 +80,7 @@ module Switchman
|
|
|
88
80
|
WHERE i.relkind = 'i'
|
|
89
81
|
AND d.indisprimary = 'f'
|
|
90
82
|
AND t.relname = '#{table_name}'
|
|
91
|
-
AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname =
|
|
83
|
+
AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = '#{shard.name}' )
|
|
92
84
|
ORDER BY i.relname
|
|
93
85
|
SQL
|
|
94
86
|
|
|
@@ -126,8 +118,6 @@ module Switchman
|
|
|
126
118
|
end
|
|
127
119
|
|
|
128
120
|
def index_name_exists?(table_name, index_name, _default = nil)
|
|
129
|
-
schema = shard.name if use_qualified_names?
|
|
130
|
-
|
|
131
121
|
exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
|
|
132
122
|
SELECT COUNT(*)
|
|
133
123
|
FROM pg_class t
|
|
@@ -136,7 +126,7 @@ module Switchman
|
|
|
136
126
|
WHERE i.relkind = 'i'
|
|
137
127
|
AND i.relname = '#{index_name}'
|
|
138
128
|
AND t.relname = '#{table_name}'
|
|
139
|
-
AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname =
|
|
129
|
+
AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = '#{shard.name}' )
|
|
140
130
|
SQL
|
|
141
131
|
end
|
|
142
132
|
|
|
@@ -150,7 +140,7 @@ module Switchman
|
|
|
150
140
|
def quote_table_name(name)
|
|
151
141
|
return quote_local_table_name(name) if @use_local_table_name
|
|
152
142
|
name = ::ActiveRecord::ConnectionAdapters::PostgreSQL::Utils.extract_schema_qualified_name(name.to_s)
|
|
153
|
-
if !name.schema
|
|
143
|
+
if !name.schema
|
|
154
144
|
name.instance_variable_set(:@schema, shard.name)
|
|
155
145
|
end
|
|
156
146
|
name.quoted
|
|
@@ -165,7 +155,6 @@ module Switchman
|
|
|
165
155
|
end
|
|
166
156
|
|
|
167
157
|
def foreign_keys(table_name)
|
|
168
|
-
schema = shard.name if use_qualified_names?
|
|
169
158
|
|
|
170
159
|
# mostly copy-pasted from AR - only change is to the nspname condition for qualified names support
|
|
171
160
|
fk_info = select_all <<-SQL.strip_heredoc
|
|
@@ -178,7 +167,7 @@ module Switchman
|
|
|
178
167
|
JOIN pg_namespace t3 ON c.connamespace = t3.oid
|
|
179
168
|
WHERE c.contype = 'f'
|
|
180
169
|
AND t1.relname = #{quote(table_name)}
|
|
181
|
-
AND t3.nspname =
|
|
170
|
+
AND t3.nspname = '#{shard.name}'
|
|
182
171
|
ORDER BY c.conname
|
|
183
172
|
SQL
|
|
184
173
|
|
|
@@ -195,7 +184,7 @@ module Switchman
|
|
|
195
184
|
# strip the schema name from to_table if it matches
|
|
196
185
|
to_table = row['to_table']
|
|
197
186
|
to_table_qualified_name = ::ActiveRecord::ConnectionAdapters::PostgreSQL::Utils.extract_schema_qualified_name(to_table)
|
|
198
|
-
if
|
|
187
|
+
if to_table_qualified_name.schema == shard.name
|
|
199
188
|
to_table = to_table_qualified_name.identifier
|
|
200
189
|
end
|
|
201
190
|
|
|
@@ -46,29 +46,16 @@ module Switchman
|
|
|
46
46
|
bind_values = bind_map.bind(params, current_shard, target_shard)
|
|
47
47
|
|
|
48
48
|
target_shard.activate(klass.shard_category) do
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
klass.find_by_sql(sql, bind_values)
|
|
52
|
-
else
|
|
53
|
-
sql = generic_query_builder(connection).sql_for(bind_values, connection)
|
|
54
|
-
klass.find_by_sql(sql, bind_values)
|
|
55
|
-
end
|
|
49
|
+
sql = qualified_query_builder(target_shard, klass).sql_for(bind_values, connection)
|
|
50
|
+
klass.find_by_sql(sql, bind_values)
|
|
56
51
|
end
|
|
57
52
|
end
|
|
58
53
|
|
|
59
54
|
if ::Rails.version < '5.2'
|
|
60
|
-
def generic_query_builder(connection)
|
|
61
|
-
@query_builder ||= connection.cacheable_query(self.class, @arel)
|
|
62
|
-
end
|
|
63
|
-
|
|
64
55
|
def qualified_query_builder(shard, klass)
|
|
65
56
|
@qualified_query_builders[shard.id] ||= klass.connection.cacheable_query(self.class, @arel)
|
|
66
57
|
end
|
|
67
58
|
else
|
|
68
|
-
def generic_query_builder(connection)
|
|
69
|
-
@query_builder ||= connection.cacheable_query(self.class, @arel).first
|
|
70
|
-
end
|
|
71
|
-
|
|
72
59
|
def qualified_query_builder(shard, klass)
|
|
73
60
|
@qualified_query_builders[shard.id] ||= klass.connection.cacheable_query(self.class, @arel).first
|
|
74
61
|
end
|
data/lib/switchman/engine.rb
CHANGED
|
@@ -122,6 +122,7 @@ module Switchman
|
|
|
122
122
|
::ActiveRecord::LogSubscriber.prepend(ActiveRecord::LogSubscriber)
|
|
123
123
|
::ActiveRecord::Migration.prepend(ActiveRecord::Migration)
|
|
124
124
|
::ActiveRecord::Migration::Compatibility::V5_0.prepend(ActiveRecord::Migration::Compatibility::V5_0)
|
|
125
|
+
::ActiveRecord::MigrationContext.prepend(ActiveRecord::MigrationContext) if ::Rails.version >= '5.2'
|
|
125
126
|
::ActiveRecord::Migrator.prepend(ActiveRecord::Migrator)
|
|
126
127
|
|
|
127
128
|
::ActiveRecord::Reflection::AbstractReflection.include(ActiveRecord::Reflection::AbstractReflection)
|
data/lib/switchman/version.rb
CHANGED
data/lib/tasks/switchman.rake
CHANGED
|
@@ -218,16 +218,9 @@ module Switchman
|
|
|
218
218
|
args = ['-s', '-x', '-O', '-f', filename]
|
|
219
219
|
args.concat(Array(extra_flags)) if extra_flags
|
|
220
220
|
search_path = configuration['schema_search_path']
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
args << "--schema=#{Shellwords.escape(shard)}"
|
|
225
|
-
elsif !search_path.blank?
|
|
226
|
-
args << search_path.split(',').map do |part|
|
|
227
|
-
"--schema=#{part.strip}"
|
|
228
|
-
end.join(' ')
|
|
229
|
-
serialized_search_path = connection.schema_search_path
|
|
230
|
-
end
|
|
221
|
+
shard = Shard.current.name
|
|
222
|
+
serialized_search_path = shard
|
|
223
|
+
args << "--schema=#{Shellwords.escape(shard)}"
|
|
231
224
|
|
|
232
225
|
args << configuration['database']
|
|
233
226
|
run_cmd('pg_dump', args, 'dumping')
|
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: 1.
|
|
4
|
+
version: 1.16.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: 2020-
|
|
13
|
+
date: 2020-09-15 00:00:00.000000000 Z
|
|
14
14
|
dependencies:
|
|
15
15
|
- !ruby/object:Gem::Dependency
|
|
16
16
|
name: railties
|
|
@@ -108,6 +108,20 @@ dependencies:
|
|
|
108
108
|
- - ">="
|
|
109
109
|
- !ruby/object:Gem::Version
|
|
110
110
|
version: '0'
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: pry
|
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - ">="
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: '0'
|
|
118
|
+
type: :development
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - ">="
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '0'
|
|
111
125
|
- !ruby/object:Gem::Dependency
|
|
112
126
|
name: pg
|
|
113
127
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -229,7 +243,7 @@ homepage: http://www.instructure.com/
|
|
|
229
243
|
licenses:
|
|
230
244
|
- MIT
|
|
231
245
|
metadata: {}
|
|
232
|
-
post_install_message:
|
|
246
|
+
post_install_message:
|
|
233
247
|
rdoc_options: []
|
|
234
248
|
require_paths:
|
|
235
249
|
- lib
|
|
@@ -245,7 +259,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
245
259
|
version: '0'
|
|
246
260
|
requirements: []
|
|
247
261
|
rubygems_version: 3.0.3
|
|
248
|
-
signing_key:
|
|
262
|
+
signing_key:
|
|
249
263
|
specification_version: 4
|
|
250
264
|
summary: Rails 4 sharding magic
|
|
251
265
|
test_files: []
|