switchman 2.0.5 → 2.0.10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/models/switchman/shard.rb +1 -0
- data/db/migrate/20180828183945_add_default_shard_index.rb +1 -1
- data/lib/switchman.rb +2 -0
- data/lib/switchman/active_record/association.rb +1 -1
- data/lib/switchman/active_record/calculations.rb +1 -1
- data/lib/switchman/active_record/connection_handler.rb +1 -1
- data/lib/switchman/active_record/migration.rb +33 -1
- data/lib/switchman/active_record/postgresql_adapter.rb +137 -108
- data/lib/switchman/active_record/query_methods.rb +73 -120
- data/lib/switchman/active_record/relation.rb +55 -3
- data/lib/switchman/connection_pool_proxy.rb +4 -0
- data/lib/switchman/schema_cache.rb +9 -1
- data/lib/switchman/standard_error.rb +1 -1
- data/lib/switchman/test_helper.rb +1 -1
- data/lib/switchman/version.rb +1 -1
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bce5f0819ee2570b0bf51ba3872af1f34c23273712cc33c35559225723fb13a0
|
4
|
+
data.tar.gz: 11e7c3d612263f94ea17bc5213580ae42e2c12743a13ff36e9f988feddda0a7e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0fad57a01e86f34533c5cf539cf92360605b129bdfdf77878fec72663c3783bad56e7a7a324f3b388e37a89296d05f309dc2c84084e30c1c44d20917e15d35e3
|
7
|
+
data.tar.gz: 002bcec40325a407c8be94432d2396c8ae812ad2828695266e591956d1e1e8195628906fce710c4681a3c72cc5f6ea8f1859a99aac22cbd3462be5dd6118bbdf
|
data/lib/switchman.rb
CHANGED
@@ -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
|
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 =
|
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.
|
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(
|
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
|
@@ -40,14 +40,6 @@ module Switchman
|
|
40
40
|
select_values("SELECT * FROM unnest(current_schemas(false))")
|
41
41
|
end
|
42
42
|
|
43
|
-
def tables(name = nil)
|
44
|
-
query(<<-SQL, 'SCHEMA').map { |row| row[0] }
|
45
|
-
SELECT tablename
|
46
|
-
FROM pg_tables
|
47
|
-
WHERE schemaname = '#{shard.name}'
|
48
|
-
SQL
|
49
|
-
end
|
50
|
-
|
51
43
|
def extract_schema_qualified_name(string)
|
52
44
|
name = ::ActiveRecord::ConnectionAdapters::PostgreSQL::Utils.extract_schema_qualified_name(string.to_s)
|
53
45
|
if string && !name.schema
|
@@ -56,80 +48,155 @@ module Switchman
|
|
56
48
|
[name.schema, name.identifier]
|
57
49
|
end
|
58
50
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
51
|
+
# significant change: use the shard name if no explicit schema
|
52
|
+
def quoted_scope(name = nil, type: nil)
|
53
|
+
schema, name = extract_schema_qualified_name(name)
|
54
|
+
type = \
|
55
|
+
case type
|
56
|
+
when "BASE TABLE"
|
57
|
+
"'r','p'"
|
58
|
+
when "VIEW"
|
59
|
+
"'v','m'"
|
60
|
+
when "FOREIGN TABLE"
|
61
|
+
"'f'"
|
62
|
+
end
|
63
|
+
scope = {}
|
64
|
+
scope[:schema] = quote(schema || shard.name)
|
65
|
+
scope[:name] = quote(name) if name
|
66
|
+
scope[:type] = type if type
|
67
|
+
scope
|
74
68
|
end
|
75
69
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
WHERE i.relkind = 'i'
|
83
|
-
AND d.indisprimary = 'f'
|
84
|
-
AND t.relname = '#{table_name}'
|
85
|
-
AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = '#{shard.name}' )
|
86
|
-
ORDER BY i.relname
|
87
|
-
SQL
|
88
|
-
|
89
|
-
|
90
|
-
result.map do |row|
|
91
|
-
index_name = row[0]
|
92
|
-
unique = row[1] == true || row[1] == 't'
|
93
|
-
indkey = row[2].split(" ")
|
94
|
-
inddef = row[3]
|
95
|
-
oid = row[4]
|
96
|
-
|
97
|
-
columns = Hash[query(<<-SQL, "SCHEMA")]
|
98
|
-
SELECT a.attnum, a.attname
|
99
|
-
FROM pg_attribute a
|
100
|
-
WHERE a.attrelid = #{oid}
|
101
|
-
AND a.attnum IN (#{indkey.join(",")})
|
70
|
+
if ::Rails.version < '6.0'
|
71
|
+
def tables(name = nil)
|
72
|
+
query(<<-SQL, 'SCHEMA').map { |row| row[0] }
|
73
|
+
SELECT tablename
|
74
|
+
FROM pg_tables
|
75
|
+
WHERE schemaname = '#{shard.name}'
|
102
76
|
SQL
|
77
|
+
end
|
103
78
|
|
104
|
-
|
105
|
-
|
106
|
-
unless
|
107
|
-
|
108
|
-
|
109
|
-
orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
|
110
|
-
where = inddef.scan(/WHERE (.+)$/).flatten[0]
|
111
|
-
using = inddef.scan(/USING (.+?) /).flatten[0].to_sym
|
112
|
-
|
113
|
-
if ::Rails.version >= "5.2"
|
114
|
-
::ActiveRecord::ConnectionAdapters::IndexDefinition.new(table_name, index_name, unique, column_names, orders: orders, where: where, using: using)
|
115
|
-
else
|
116
|
-
::ActiveRecord::ConnectionAdapters::IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where, nil, using)
|
117
|
-
end
|
79
|
+
def view_exists?(name)
|
80
|
+
name = ::ActiveRecord::ConnectionAdapters::PostgreSQL::Utils.extract_schema_qualified_name(name.to_s)
|
81
|
+
return false unless name.identifier
|
82
|
+
if !name.schema
|
83
|
+
name.instance_variable_set(:@schema, shard.name)
|
118
84
|
end
|
119
|
-
end.compact
|
120
|
-
end
|
121
85
|
|
122
|
-
|
123
|
-
|
124
|
-
|
86
|
+
select_values(<<-SQL, 'SCHEMA').any?
|
87
|
+
SELECT c.relname
|
88
|
+
FROM pg_class c
|
89
|
+
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
|
90
|
+
WHERE c.relkind IN ('v','m') -- (v)iew, (m)aterialized view
|
91
|
+
AND c.relname = '#{name.identifier}'
|
92
|
+
AND n.nspname = '#{shard.name}'
|
93
|
+
SQL
|
94
|
+
end
|
95
|
+
|
96
|
+
def indexes(table_name)
|
97
|
+
result = query(<<-SQL, 'SCHEMA')
|
98
|
+
SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
|
125
99
|
FROM pg_class t
|
126
100
|
INNER JOIN pg_index d ON t.oid = d.indrelid
|
127
101
|
INNER JOIN pg_class i ON d.indexrelid = i.oid
|
128
102
|
WHERE i.relkind = 'i'
|
129
|
-
AND
|
103
|
+
AND d.indisprimary = 'f'
|
130
104
|
AND t.relname = '#{table_name}'
|
131
105
|
AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = '#{shard.name}' )
|
132
|
-
|
106
|
+
ORDER BY i.relname
|
107
|
+
SQL
|
108
|
+
|
109
|
+
|
110
|
+
result.map do |row|
|
111
|
+
index_name = row[0]
|
112
|
+
unique = row[1] == true || row[1] == 't'
|
113
|
+
indkey = row[2].split(" ")
|
114
|
+
inddef = row[3]
|
115
|
+
oid = row[4]
|
116
|
+
|
117
|
+
columns = Hash[query(<<-SQL, "SCHEMA")]
|
118
|
+
SELECT a.attnum, a.attname
|
119
|
+
FROM pg_attribute a
|
120
|
+
WHERE a.attrelid = #{oid}
|
121
|
+
AND a.attnum IN (#{indkey.join(",")})
|
122
|
+
SQL
|
123
|
+
|
124
|
+
column_names = columns.stringify_keys.values_at(*indkey).compact
|
125
|
+
|
126
|
+
unless column_names.empty?
|
127
|
+
# add info on sort order for columns (only desc order is explicitly specified, asc is the default)
|
128
|
+
desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
|
129
|
+
orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
|
130
|
+
where = inddef.scan(/WHERE (.+)$/).flatten[0]
|
131
|
+
using = inddef.scan(/USING (.+?) /).flatten[0].to_sym
|
132
|
+
|
133
|
+
if ::Rails.version >= "5.2"
|
134
|
+
::ActiveRecord::ConnectionAdapters::IndexDefinition.new(table_name, index_name, unique, column_names, orders: orders, where: where, using: using)
|
135
|
+
else
|
136
|
+
::ActiveRecord::ConnectionAdapters::IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where, nil, using)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end.compact
|
140
|
+
end
|
141
|
+
|
142
|
+
def index_name_exists?(table_name, index_name, _default = nil)
|
143
|
+
exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
|
144
|
+
SELECT COUNT(*)
|
145
|
+
FROM pg_class t
|
146
|
+
INNER JOIN pg_index d ON t.oid = d.indrelid
|
147
|
+
INNER JOIN pg_class i ON d.indexrelid = i.oid
|
148
|
+
WHERE i.relkind = 'i'
|
149
|
+
AND i.relname = '#{index_name}'
|
150
|
+
AND t.relname = '#{table_name}'
|
151
|
+
AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = '#{shard.name}' )
|
152
|
+
SQL
|
153
|
+
end
|
154
|
+
|
155
|
+
def foreign_keys(table_name)
|
156
|
+
# mostly copy-pasted from AR - only change is to the nspname condition for qualified names support
|
157
|
+
fk_info = select_all <<-SQL.strip_heredoc
|
158
|
+
SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete
|
159
|
+
FROM pg_constraint c
|
160
|
+
JOIN pg_class t1 ON c.conrelid = t1.oid
|
161
|
+
JOIN pg_class t2 ON c.confrelid = t2.oid
|
162
|
+
JOIN pg_attribute a1 ON a1.attnum = c.conkey[1] AND a1.attrelid = t1.oid
|
163
|
+
JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid
|
164
|
+
JOIN pg_namespace t3 ON c.connamespace = t3.oid
|
165
|
+
WHERE c.contype = 'f'
|
166
|
+
AND t1.relname = #{quote(table_name)}
|
167
|
+
AND t3.nspname = '#{shard.name}'
|
168
|
+
ORDER BY c.conname
|
169
|
+
SQL
|
170
|
+
|
171
|
+
fk_info.map do |row|
|
172
|
+
options = {
|
173
|
+
column: row['column'],
|
174
|
+
name: row['name'],
|
175
|
+
primary_key: row['primary_key']
|
176
|
+
}
|
177
|
+
|
178
|
+
options[:on_delete] = extract_foreign_key_action(row['on_delete'])
|
179
|
+
options[:on_update] = extract_foreign_key_action(row['on_update'])
|
180
|
+
|
181
|
+
# strip the schema name from to_table if it matches
|
182
|
+
to_table = row['to_table']
|
183
|
+
to_table_qualified_name = ::ActiveRecord::ConnectionAdapters::PostgreSQL::Utils.extract_schema_qualified_name(to_table)
|
184
|
+
if to_table_qualified_name.schema == shard.name
|
185
|
+
to_table = to_table_qualified_name.identifier
|
186
|
+
end
|
187
|
+
|
188
|
+
::ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(table_name, to_table, options)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
else
|
192
|
+
def foreign_keys(table_name)
|
193
|
+
super.each do |fk|
|
194
|
+
to_table_qualified_name = ::ActiveRecord::ConnectionAdapters::PostgreSQL::Utils.extract_schema_qualified_name(fk.to_table)
|
195
|
+
if to_table_qualified_name.schema == shard.name
|
196
|
+
fk.to_table = to_table_qualified_name.identifier
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
133
200
|
end
|
134
201
|
|
135
202
|
def quote_local_table_name(name)
|
@@ -156,44 +223,6 @@ module Switchman
|
|
156
223
|
@use_local_table_name = old_value
|
157
224
|
end
|
158
225
|
|
159
|
-
def foreign_keys(table_name)
|
160
|
-
|
161
|
-
# mostly copy-pasted from AR - only change is to the nspname condition for qualified names support
|
162
|
-
fk_info = select_all <<-SQL.strip_heredoc
|
163
|
-
SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete
|
164
|
-
FROM pg_constraint c
|
165
|
-
JOIN pg_class t1 ON c.conrelid = t1.oid
|
166
|
-
JOIN pg_class t2 ON c.confrelid = t2.oid
|
167
|
-
JOIN pg_attribute a1 ON a1.attnum = c.conkey[1] AND a1.attrelid = t1.oid
|
168
|
-
JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid
|
169
|
-
JOIN pg_namespace t3 ON c.connamespace = t3.oid
|
170
|
-
WHERE c.contype = 'f'
|
171
|
-
AND t1.relname = #{quote(table_name)}
|
172
|
-
AND t3.nspname = '#{shard.name}'
|
173
|
-
ORDER BY c.conname
|
174
|
-
SQL
|
175
|
-
|
176
|
-
fk_info.map do |row|
|
177
|
-
options = {
|
178
|
-
column: row['column'],
|
179
|
-
name: row['name'],
|
180
|
-
primary_key: row['primary_key']
|
181
|
-
}
|
182
|
-
|
183
|
-
options[:on_delete] = extract_foreign_key_action(row['on_delete'])
|
184
|
-
options[:on_update] = extract_foreign_key_action(row['on_update'])
|
185
|
-
|
186
|
-
# strip the schema name from to_table if it matches
|
187
|
-
to_table = row['to_table']
|
188
|
-
to_table_qualified_name = ::ActiveRecord::ConnectionAdapters::PostgreSQL::Utils.extract_schema_qualified_name(to_table)
|
189
|
-
if to_table_qualified_name.schema == shard.name
|
190
|
-
to_table = to_table_qualified_name.identifier
|
191
|
-
end
|
192
|
-
|
193
|
-
::ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(table_name, to_table, options)
|
194
|
-
end
|
195
|
-
end
|
196
|
-
|
197
226
|
def add_index_options(_table_name, _column_name, **)
|
198
227
|
index_name, index_type, index_columns, index_options, algorithm, using = super
|
199
228
|
algorithm = nil if DatabaseServer.creating_new_shard && algorithm == "CONCURRENTLY"
|
@@ -78,10 +78,6 @@ module Switchman
|
|
78
78
|
end
|
79
79
|
end
|
80
80
|
|
81
|
-
def or(other)
|
82
|
-
super(other.shard(self.primary_shard))
|
83
|
-
end
|
84
|
-
|
85
81
|
private
|
86
82
|
|
87
83
|
if ::Rails.version >= '5.2'
|
@@ -246,135 +242,92 @@ module Switchman
|
|
246
242
|
binds: nil,
|
247
243
|
dup_binds_on_mutation: false)
|
248
244
|
result = predicates.map do |predicate|
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
result = [result, binds]
|
254
|
-
result
|
255
|
-
end
|
245
|
+
next predicate unless predicate.is_a?(::Arel::Nodes::Binary)
|
246
|
+
next predicate unless predicate.left.is_a?(::Arel::Attributes::Attribute)
|
247
|
+
relation, column = relation_and_column(predicate.left)
|
248
|
+
next predicate unless (type = transposable_attribute_type(relation, column))
|
256
249
|
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
binds: nil,
|
262
|
-
dup_binds_on_mutation: false)
|
263
|
-
if predicate.is_a?(::Arel::Nodes::Grouping)
|
264
|
-
return predicate, binds unless predicate.expr.is_a?(::Arel::Nodes::Or)
|
265
|
-
# Dang, we have an OR. OK, that means we have other epxressions below this
|
266
|
-
# level, perhaps many, that may need transposition.
|
267
|
-
# the left side and right side must each be treated as predicate lists and
|
268
|
-
# transformed in kind, if neither of them changes we can just return the grouping as is.
|
269
|
-
# hold on, it's about to get recursive...
|
270
|
-
#
|
271
|
-
# TODO: "binds" is getting passed up and down
|
272
|
-
# this stack purely because of the necessary handling for rails <5.2
|
273
|
-
# Dropping support for 5.2 means we can remove the "binds" argument from
|
274
|
-
# all of this and yank the conditional below where we monkey with their instance state.
|
275
|
-
or_expr = predicate.expr
|
276
|
-
left_node = or_expr.left
|
277
|
-
right_node = or_expr.right
|
278
|
-
left_predicates = left_node.children
|
279
|
-
right_predicates = right_node.children
|
280
|
-
new_left_predicates, binds = transpose_predicates(left_predicates, source_shard,
|
281
|
-
target_shard, remove_nonlocal_primary_keys,
|
282
|
-
binds: binds, dup_binds_on_mutation: dup_binds_on_mutation)
|
283
|
-
new_right_predicates, binds = transpose_predicates(right_predicates, source_shard,
|
284
|
-
target_shard, remove_nonlocal_primary_keys,
|
285
|
-
binds: binds, dup_binds_on_mutation: dup_binds_on_mutation)
|
286
|
-
if new_left_predicates != left_predicates
|
287
|
-
left_node.instance_variable_set(:@children, new_left_predicates)
|
288
|
-
end
|
289
|
-
if new_right_predicates != right_predicates
|
290
|
-
right_node.instance_variable_set(:@children, new_right_predicates)
|
291
|
-
end
|
292
|
-
return predicate, binds
|
293
|
-
end
|
294
|
-
return predicate, binds unless predicate.is_a?(::Arel::Nodes::Binary)
|
295
|
-
return predicate, binds unless predicate.left.is_a?(::Arel::Attributes::Attribute)
|
296
|
-
relation, column = relation_and_column(predicate.left)
|
297
|
-
return predicate, binds unless (type = transposable_attribute_type(relation, column))
|
250
|
+
remove = true if type == :primary &&
|
251
|
+
remove_nonlocal_primary_keys &&
|
252
|
+
predicate.left.relation.model == klass &&
|
253
|
+
(predicate.is_a?(::Arel::Nodes::Equality) || predicate.is_a?(::Arel::Nodes::In))
|
298
254
|
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
elsif type == :primary
|
308
|
-
Shard.current(klass.shard_category)
|
309
|
-
elsif type == :foreign
|
310
|
-
source_shard_for_foreign_key(relation, column)
|
311
|
-
end
|
255
|
+
current_source_shard =
|
256
|
+
if source_shard
|
257
|
+
source_shard
|
258
|
+
elsif type == :primary
|
259
|
+
Shard.current(klass.shard_category)
|
260
|
+
elsif type == :foreign
|
261
|
+
source_shard_for_foreign_key(relation, column)
|
262
|
+
end
|
312
263
|
|
313
|
-
|
314
|
-
|
315
|
-
|
264
|
+
if ::Rails.version >= "5.2"
|
265
|
+
new_right_value =
|
266
|
+
case predicate.right
|
267
|
+
when Array
|
268
|
+
predicate.right.map {|val| transpose_predicate_value(val, current_source_shard, target_shard, type, remove).presence }.compact
|
269
|
+
else
|
270
|
+
transpose_predicate_value(predicate.right, current_source_shard, target_shard, type, remove)
|
271
|
+
end
|
272
|
+
else
|
273
|
+
new_right_value = case predicate.right
|
316
274
|
when Array
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
unless remove && local_id > Shard::IDS_PER_SHARD
|
329
|
-
if value.is_a?(::Arel::Nodes::Casted)
|
330
|
-
if local_id == value.val
|
331
|
-
local_id = value
|
332
|
-
elsif local_id != value
|
333
|
-
local_id = value.class.new(local_id, value.attribute)
|
275
|
+
local_ids = []
|
276
|
+
predicate.right.each do |value|
|
277
|
+
local_id = Shard.relative_id_for(value, current_source_shard, target_shard)
|
278
|
+
next unless local_id
|
279
|
+
unless remove && local_id > Shard::IDS_PER_SHARD
|
280
|
+
if value.is_a?(::Arel::Nodes::Casted)
|
281
|
+
if local_id == value.val
|
282
|
+
local_id = value
|
283
|
+
elsif local_id != value
|
284
|
+
local_id = value.class.new(local_id, value.attribute)
|
285
|
+
end
|
334
286
|
end
|
287
|
+
local_ids << local_id
|
335
288
|
end
|
336
|
-
local_ids << local_id
|
337
|
-
end
|
338
|
-
end
|
339
|
-
local_ids
|
340
|
-
when ::Arel::Nodes::BindParam
|
341
|
-
# look for a bind param with a matching column name
|
342
|
-
if binds && bind = binds.detect{|b| b&.name.to_s == predicate.left.name.to_s}
|
343
|
-
# before we mutate, dup
|
344
|
-
if dup_binds_on_mutation
|
345
|
-
binds = binds.map(&:dup)
|
346
|
-
dup_binds_on_mutation = false
|
347
|
-
bind = binds.find { |b| b&.name.to_s == predicate.left.name.to_s }
|
348
289
|
end
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
290
|
+
local_ids
|
291
|
+
when ::Arel::Nodes::BindParam
|
292
|
+
# look for a bind param with a matching column name
|
293
|
+
if binds && bind = binds.detect{|b| b&.name.to_s == predicate.left.name.to_s}
|
294
|
+
# before we mutate, dup
|
295
|
+
if dup_binds_on_mutation
|
296
|
+
binds = binds.map(&:dup)
|
297
|
+
dup_binds_on_mutation = false
|
298
|
+
bind = binds.find { |b| b&.name.to_s == predicate.left.name.to_s }
|
299
|
+
end
|
300
|
+
if bind.value.is_a?(::ActiveRecord::StatementCache::Substitute)
|
301
|
+
bind.value.sharded = true # mark for transposition later
|
302
|
+
bind.value.primary = true if type == :primary
|
303
|
+
else
|
304
|
+
local_id = Shard.relative_id_for(bind.value, current_source_shard, target_shard)
|
305
|
+
local_id = [] if remove && local_id > Shard::IDS_PER_SHARD
|
306
|
+
bind.instance_variable_set(:@value, local_id)
|
307
|
+
bind.instance_variable_set(:@value_for_database, nil)
|
308
|
+
end
|
357
309
|
end
|
310
|
+
predicate.right
|
311
|
+
else
|
312
|
+
local_id = Shard.relative_id_for(predicate.right, current_source_shard, target_shard) || predicate.right
|
313
|
+
local_id = [] if remove && local_id.is_a?(Integer) && local_id > Shard::IDS_PER_SHARD
|
314
|
+
local_id
|
358
315
|
end
|
359
|
-
predicate.right
|
360
|
-
else
|
361
|
-
local_id = Shard.relative_id_for(predicate.right, current_source_shard, target_shard) || predicate.right
|
362
|
-
local_id = [] if remove && local_id.is_a?(Integer) && local_id > Shard::IDS_PER_SHARD
|
363
|
-
local_id
|
364
316
|
end
|
365
|
-
|
366
|
-
out_predicate = if new_right_value == predicate.right
|
367
|
-
predicate
|
368
|
-
elsif predicate.right.is_a?(::Arel::Nodes::Casted)
|
369
|
-
if new_right_value == predicate.right.val
|
317
|
+
if new_right_value == predicate.right
|
370
318
|
predicate
|
319
|
+
elsif predicate.right.is_a?(::Arel::Nodes::Casted)
|
320
|
+
if new_right_value == predicate.right.val
|
321
|
+
predicate
|
322
|
+
else
|
323
|
+
predicate.class.new(predicate.left, predicate.right.class.new(new_right_value, predicate.right.attribute))
|
324
|
+
end
|
371
325
|
else
|
372
|
-
predicate.class.new(predicate.left,
|
326
|
+
predicate.class.new(predicate.left, new_right_value)
|
373
327
|
end
|
374
|
-
else
|
375
|
-
predicate.class.new(predicate.left, new_right_value)
|
376
328
|
end
|
377
|
-
|
329
|
+
result = [result, binds]
|
330
|
+
result
|
378
331
|
end
|
379
332
|
|
380
333
|
def transpose_predicate_value(value, current_shard, target_shard, attribute_type, remove_non_local_ids)
|
@@ -387,7 +340,7 @@ module Switchman
|
|
387
340
|
value
|
388
341
|
else
|
389
342
|
local_id = Shard.relative_id_for(current_id, current_shard, target_shard) || current_id
|
390
|
-
|
343
|
+
return nil if remove_non_local_ids && local_id.is_a?(Integer) && local_id > Shard::IDS_PER_SHARD
|
391
344
|
if current_id != local_id
|
392
345
|
# make a new bind param
|
393
346
|
::Arel::Nodes::BindParam.new(query_att.class.new(query_att.name, local_id, query_att.type))
|
@@ -106,10 +106,62 @@ module Switchman
|
|
106
106
|
shards.first.activate(klass.shard_category) { yield(self, shards.first) }
|
107
107
|
end
|
108
108
|
else
|
109
|
-
|
110
|
-
|
111
|
-
|
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?
|
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.respond_to?(ov.expr.name)
|
146
|
+
a = l.send(ov.expr.name)
|
147
|
+
b = r.send(ov.expr.name)
|
148
|
+
else
|
149
|
+
a = l.attributes[ov.expr.name]
|
150
|
+
b = r.attributes[ov.expr.name]
|
151
|
+
end
|
152
|
+
next if a == b
|
153
|
+
|
154
|
+
if a.nil? || b.nil?
|
155
|
+
result = 1 if a.nil?
|
156
|
+
result *= -1 if ov.is_a?(::Arel::Nodes::Descending)
|
157
|
+
else
|
158
|
+
result = a <=> b
|
159
|
+
end
|
160
|
+
|
161
|
+
result *= -1 if ov.is_a?(::Arel::Nodes::Descending)
|
162
|
+
break unless result.zero?
|
112
163
|
end
|
164
|
+
result
|
113
165
|
end
|
114
166
|
end
|
115
167
|
end
|
@@ -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
|
-
|
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
|
data/lib/switchman/version.rb
CHANGED
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.
|
4
|
+
version: 2.0.10
|
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-
|
13
|
+
date: 2021-06-09 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.
|
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: []
|