switchman 3.4.2 → 3.6.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Rakefile +15 -14
- data/db/migrate/20180828183945_add_default_shard_index.rb +1 -1
- data/db/migrate/20190114212900_add_unique_name_indexes.rb +10 -4
- data/lib/switchman/active_record/abstract_adapter.rb +4 -2
- data/lib/switchman/active_record/associations.rb +89 -16
- data/lib/switchman/active_record/attribute_methods.rb +67 -22
- data/lib/switchman/active_record/base.rb +112 -22
- data/lib/switchman/active_record/calculations.rb +93 -37
- data/lib/switchman/active_record/connection_handler.rb +18 -0
- data/lib/switchman/active_record/connection_pool.rb +18 -14
- data/lib/switchman/active_record/database_configurations.rb +37 -15
- data/lib/switchman/active_record/finder_methods.rb +44 -14
- data/lib/switchman/active_record/log_subscriber.rb +11 -5
- data/lib/switchman/active_record/migration.rb +28 -9
- data/lib/switchman/active_record/pending_migration_connection.rb +17 -0
- data/lib/switchman/active_record/persistence.rb +22 -0
- data/lib/switchman/active_record/postgresql_adapter.rb +11 -10
- data/lib/switchman/active_record/predicate_builder.rb +2 -2
- data/lib/switchman/active_record/query_cache.rb +49 -20
- data/lib/switchman/active_record/query_methods.rb +93 -30
- data/lib/switchman/active_record/relation.rb +22 -11
- data/lib/switchman/active_record/spawn_methods.rb +2 -2
- data/lib/switchman/active_record/statement_cache.rb +2 -2
- data/lib/switchman/active_record/tasks/database_tasks.rb +6 -1
- data/lib/switchman/active_record/test_fixtures.rb +26 -16
- data/lib/switchman/active_support/cache.rb +9 -4
- data/lib/switchman/arel.rb +34 -18
- data/lib/switchman/call_super.rb +2 -8
- data/lib/switchman/database_server.rb +68 -21
- data/lib/switchman/default_shard.rb +14 -3
- data/lib/switchman/engine.rb +39 -19
- data/lib/switchman/environment.rb +2 -2
- data/lib/switchman/errors.rb +4 -1
- data/lib/switchman/guard_rail/relation.rb +1 -2
- data/lib/switchman/parallel.rb +5 -5
- data/lib/switchman/r_spec_helper.rb +11 -11
- data/lib/switchman/shard.rb +166 -64
- data/lib/switchman/sharded_instrumenter.rb +7 -3
- data/lib/switchman/standard_error.rb +4 -0
- data/lib/switchman/test_helper.rb +2 -2
- data/lib/switchman/version.rb +1 -1
- data/lib/switchman.rb +27 -15
- data/lib/tasks/switchman.rake +117 -51
- metadata +19 -44
@@ -42,26 +42,56 @@ module Switchman
|
|
42
42
|
primary_shard.activate { super }
|
43
43
|
end
|
44
44
|
|
45
|
-
|
46
|
-
conditions =
|
47
|
-
|
45
|
+
if ::Rails.version < "7.1"
|
46
|
+
def exists?(conditions = :none)
|
47
|
+
conditions = conditions.id if ::ActiveRecord::Base === conditions
|
48
|
+
return false unless conditions
|
48
49
|
|
49
|
-
|
50
|
-
|
50
|
+
relation = apply_join_dependency(eager_loading: false)
|
51
|
+
return false if ::ActiveRecord::NullRelation === relation
|
51
52
|
|
52
|
-
|
53
|
+
relation = relation.except(:select, :order).select("1 AS one").limit(1)
|
53
54
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
55
|
+
case conditions
|
56
|
+
when Array, Hash
|
57
|
+
relation = relation.where(conditions)
|
58
|
+
else
|
59
|
+
relation = relation.where(table[primary_key].eq(conditions)) if conditions != :none
|
60
|
+
end
|
61
|
+
|
62
|
+
relation.activate do |shard_rel|
|
63
|
+
return true if connection.select_value(shard_rel.arel, "#{name} Exists")
|
64
|
+
end
|
65
|
+
false
|
59
66
|
end
|
67
|
+
else
|
68
|
+
def exists?(conditions = :none)
|
69
|
+
return false if @none
|
70
|
+
|
71
|
+
if Base === conditions
|
72
|
+
raise ArgumentError, <<-TEXT.squish
|
73
|
+
You are passing an instance of ActiveRecord::Base to `exists?`.
|
74
|
+
Please pass the id of the object by calling `.id`.
|
75
|
+
TEXT
|
76
|
+
end
|
60
77
|
|
61
|
-
|
62
|
-
|
78
|
+
return false if !conditions || limit_value == 0 # rubocop:disable Style/NumericPredicate
|
79
|
+
|
80
|
+
if eager_loading?
|
81
|
+
relation = apply_join_dependency(eager_loading: false)
|
82
|
+
return relation.exists?(conditions)
|
83
|
+
end
|
84
|
+
|
85
|
+
relation = construct_relation_for_exists(conditions)
|
86
|
+
return false if relation.where_clause.contradiction?
|
87
|
+
|
88
|
+
relation.activate do |shard_rel|
|
89
|
+
return true if skip_query_cache_if_necessary do
|
90
|
+
connection.select_rows(shard_rel.arel, "#{name} Exists?").size == 1
|
91
|
+
end
|
92
|
+
end
|
93
|
+
false
|
63
94
|
end
|
64
|
-
false
|
65
95
|
end
|
66
96
|
end
|
67
97
|
end
|
@@ -5,8 +5,10 @@ module Switchman
|
|
5
5
|
module LogSubscriber
|
6
6
|
# sadly, have to completely replace this
|
7
7
|
def sql(event)
|
8
|
-
|
9
|
-
|
8
|
+
if ::Rails.version < "7.1"
|
9
|
+
self.class.runtime += event.duration
|
10
|
+
return unless logger.debug?
|
11
|
+
end
|
10
12
|
|
11
13
|
payload = event.payload
|
12
14
|
|
@@ -14,20 +16,24 @@ module Switchman
|
|
14
16
|
|
15
17
|
name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
|
16
18
|
name = "CACHE #{name}" if payload[:cached]
|
17
|
-
sql = payload[:sql].squeeze(
|
19
|
+
sql = payload[:sql].squeeze(" ")
|
18
20
|
binds = nil
|
19
21
|
shard = payload[:shard]
|
20
22
|
shard = " [#{shard[:database_server_id]}:#{shard[:id]} #{shard[:env]}]" if shard
|
21
23
|
|
22
24
|
unless (payload[:binds] || []).empty?
|
23
25
|
casted_params = type_casted_binds(payload[:type_casted_binds])
|
24
|
-
binds =
|
26
|
+
binds = " " + payload[:binds].zip(casted_params).map do |attr, value|
|
25
27
|
render_bind(attr, value)
|
26
28
|
end.inspect
|
27
29
|
end
|
28
30
|
|
29
31
|
name = colorize_payload_name(name, payload[:name])
|
30
|
-
sql =
|
32
|
+
sql = if ::Rails.version < "7.1"
|
33
|
+
color(sql, sql_color(sql), true)
|
34
|
+
else
|
35
|
+
color(sql, sql_color(sql), bold: true)
|
36
|
+
end
|
31
37
|
|
32
38
|
debug " #{name} #{sql}#{binds}#{shard}"
|
33
39
|
end
|
@@ -14,7 +14,9 @@ module Switchman
|
|
14
14
|
|
15
15
|
def connection
|
16
16
|
conn = super
|
17
|
-
|
17
|
+
if conn.shard != ::ActiveRecord::Base.current_switchman_shard
|
18
|
+
::ActiveRecord::Base.connection_pool.switch_database(conn)
|
19
|
+
end
|
18
20
|
conn
|
19
21
|
end
|
20
22
|
end
|
@@ -27,7 +29,11 @@ module Switchman
|
|
27
29
|
db_name_hash = Zlib.crc32(Shard.current.name)
|
28
30
|
shard_name_hash = ::ActiveRecord::Migrator::MIGRATOR_SALT * db_name_hash
|
29
31
|
# Store in internalmetadata to allow other tools to be able to lock out migrations
|
30
|
-
::
|
32
|
+
if ::Rails.version < "7.1"
|
33
|
+
::ActiveRecord::InternalMetadata[:migrator_advisory_lock_id] = shard_name_hash
|
34
|
+
else
|
35
|
+
::ActiveRecord::InternalMetadata.new(connection)[:migrator_advisory_lock_id] = shard_name_hash
|
36
|
+
end
|
31
37
|
shard_name_hash
|
32
38
|
end
|
33
39
|
|
@@ -46,17 +52,30 @@ module Switchman
|
|
46
52
|
module MigrationContext
|
47
53
|
def migrate(...)
|
48
54
|
connection = ::ActiveRecord::Base.connection
|
49
|
-
|
50
|
-
|
51
|
-
|
55
|
+
schema_cache_holder = ::ActiveRecord::Base.connection_pool
|
56
|
+
schema_cache_holder = schema_cache_holder.schema_reflection if ::Rails.version >= "7.1"
|
57
|
+
previous_schema_cache = if ::Rails.version < "7.1"
|
58
|
+
schema_cache_holder.get_schema_cache(connection)
|
59
|
+
else
|
60
|
+
schema_cache_holder.instance_variable_get(:@cache)
|
61
|
+
end
|
52
62
|
|
53
|
-
|
54
|
-
|
63
|
+
if ::Rails.version < "7.1"
|
64
|
+
temporary_schema_cache = ::ActiveRecord::ConnectionAdapters::SchemaCache.new(connection)
|
65
|
+
|
66
|
+
reset_column_information
|
67
|
+
schema_cache_holder.set_schema_cache(temporary_schema_cache)
|
68
|
+
else
|
69
|
+
schema_cache_holder.instance_variable_get(:@cache)
|
70
|
+
|
71
|
+
reset_column_information
|
72
|
+
schema_cache_holder.clear!
|
73
|
+
end
|
55
74
|
|
56
75
|
begin
|
57
76
|
super(...)
|
58
77
|
ensure
|
59
|
-
|
78
|
+
schema_cache_holder.set_schema_cache(previous_schema_cache)
|
60
79
|
reset_column_information
|
61
80
|
end
|
62
81
|
end
|
@@ -65,7 +84,7 @@ module Switchman
|
|
65
84
|
return @migrations if instance_variable_defined?(:@migrations)
|
66
85
|
|
67
86
|
migrations_cache = Thread.current[:migrations_cache] ||= {}
|
68
|
-
key = Digest::MD5.hexdigest(migration_files.sort.join(
|
87
|
+
key = Digest::MD5.hexdigest(migration_files.sort.join(","))
|
69
88
|
@migrations = migrations_cache[key] ||= super
|
70
89
|
end
|
71
90
|
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Switchman
|
4
|
+
module ActiveRecord
|
5
|
+
module PendingMigrationConnection
|
6
|
+
module ClassMethods
|
7
|
+
def current_role
|
8
|
+
::ActiveRecord::Base.current_role
|
9
|
+
end
|
10
|
+
|
11
|
+
def current_switchman_shard
|
12
|
+
::ActiveRecord::Base.current_switchman_shard
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -5,10 +5,12 @@ module Switchman
|
|
5
5
|
module Persistence
|
6
6
|
# touch reads the id attribute directly, so it's not relative to the current shard
|
7
7
|
def touch(*, **)
|
8
|
+
writable_shadow_record_warning
|
8
9
|
shard.activate(self.class.connection_class_for_self) { super }
|
9
10
|
end
|
10
11
|
|
11
12
|
def update_columns(*)
|
13
|
+
writable_shadow_record_warning
|
12
14
|
shard.activate(self.class.connection_class_for_self) { super }
|
13
15
|
end
|
14
16
|
|
@@ -17,13 +19,33 @@ module Switchman
|
|
17
19
|
db.unguard { super }
|
18
20
|
end
|
19
21
|
|
22
|
+
def destroy
|
23
|
+
writable_shadow_record_warning
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
27
|
+
def create_or_update(**, &block)
|
28
|
+
writable_shadow_record_warning
|
29
|
+
super
|
30
|
+
end
|
31
|
+
|
20
32
|
def reload(*)
|
21
33
|
res = super
|
22
34
|
# When a shadow record is reloaded the real record is returned. So
|
23
35
|
# we need to ensure the loaded_from_shard is set correctly after a reload.
|
24
36
|
@loaded_from_shard = @shard
|
37
|
+
if @readonly_from_shadow
|
38
|
+
@readonly_from_shadow = false
|
39
|
+
@readonly = false
|
40
|
+
end
|
25
41
|
res
|
26
42
|
end
|
43
|
+
|
44
|
+
def writable_shadow_record_warning
|
45
|
+
return unless shadow_record? && Switchman.config[:writable_shadow_records]
|
46
|
+
|
47
|
+
Switchman::Deprecation.warn("writing to shadow records is not supported")
|
48
|
+
end
|
27
49
|
end
|
28
50
|
end
|
29
51
|
end
|
@@ -5,9 +5,9 @@ module Switchman
|
|
5
5
|
module PostgreSQLAdapter
|
6
6
|
# copy/paste; use quote_local_table_name
|
7
7
|
def create_database(name, options = {})
|
8
|
-
options = { encoding:
|
8
|
+
options = { encoding: "utf8" }.merge!(options.symbolize_keys)
|
9
9
|
|
10
|
-
option_string = options.sum(
|
10
|
+
option_string = options.sum("") do |key, value|
|
11
11
|
case key
|
12
12
|
when :owner
|
13
13
|
" OWNER = \"#{value}\""
|
@@ -24,7 +24,7 @@ module Switchman
|
|
24
24
|
when :connection_limit
|
25
25
|
" CONNECTION LIMIT = #{value}"
|
26
26
|
else
|
27
|
-
|
27
|
+
""
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
@@ -37,7 +37,7 @@ module Switchman
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def current_schemas
|
40
|
-
select_values(
|
40
|
+
select_values("SELECT * FROM unnest(current_schemas(false))")
|
41
41
|
end
|
42
42
|
|
43
43
|
def extract_schema_qualified_name(string)
|
@@ -49,13 +49,13 @@ module Switchman
|
|
49
49
|
# significant change: use the shard name if no explicit schema
|
50
50
|
def quoted_scope(name = nil, type: nil)
|
51
51
|
schema, name = extract_schema_qualified_name(name)
|
52
|
-
type =
|
52
|
+
type =
|
53
53
|
case type # rubocop:disable Style/HashLikeCase
|
54
|
-
when
|
54
|
+
when "BASE TABLE"
|
55
55
|
"'r','p'"
|
56
|
-
when
|
56
|
+
when "VIEW"
|
57
57
|
"'v','m'"
|
58
|
-
when
|
58
|
+
when "FOREIGN TABLE"
|
59
59
|
"'f'"
|
60
60
|
end
|
61
61
|
scope = {}
|
@@ -67,7 +67,8 @@ module Switchman
|
|
67
67
|
|
68
68
|
def foreign_keys(table_name)
|
69
69
|
super.each do |fk|
|
70
|
-
to_table_qualified_name =
|
70
|
+
to_table_qualified_name =
|
71
|
+
::ActiveRecord::ConnectionAdapters::PostgreSQL::Utils.extract_schema_qualified_name(fk.to_table)
|
71
72
|
fk.to_table = to_table_qualified_name.identifier if to_table_qualified_name.schema == shard.name
|
72
73
|
end
|
73
74
|
end
|
@@ -101,7 +102,7 @@ module Switchman
|
|
101
102
|
|
102
103
|
def add_index_options(_table_name, _column_name, **)
|
103
104
|
index, algorithm, if_not_exists = super
|
104
|
-
algorithm = nil if DatabaseServer.creating_new_shard && algorithm ==
|
105
|
+
algorithm = nil if DatabaseServer.creating_new_shard && algorithm == "CONCURRENTLY"
|
105
106
|
[index, algorithm, if_not_exists]
|
106
107
|
end
|
107
108
|
|
@@ -5,28 +5,57 @@ module Switchman
|
|
5
5
|
module QueryCache
|
6
6
|
private
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
8
|
+
if ::Rails.version < "7.1"
|
9
|
+
def cache_sql(sql, name, binds)
|
10
|
+
# have to include the shard id in the cache key because of switching dbs on the same connection
|
11
|
+
sql = "#{shard.id}::#{sql}"
|
12
|
+
@lock.synchronize do
|
13
|
+
result =
|
14
|
+
if query_cache[sql].key?(binds)
|
15
|
+
args = {
|
16
|
+
sql: sql,
|
17
|
+
binds: binds,
|
18
|
+
name: name,
|
19
|
+
connection_id: object_id,
|
20
|
+
cached: true,
|
21
|
+
type_casted_binds: -> { type_casted_binds(binds) }
|
22
|
+
}
|
23
|
+
::ActiveSupport::Notifications.instrument(
|
24
|
+
"sql.active_record",
|
25
|
+
args
|
26
|
+
)
|
27
|
+
query_cache[sql][binds]
|
28
|
+
else
|
29
|
+
query_cache[sql][binds] = yield
|
30
|
+
end
|
31
|
+
result.dup
|
32
|
+
end
|
33
|
+
end
|
34
|
+
else
|
35
|
+
def cache_sql(sql, name, binds)
|
36
|
+
# have to include the shard id in the cache key because of switching dbs on the same connection
|
37
|
+
sql = "#{shard.id}::#{sql}"
|
38
|
+
key = binds.empty? ? sql : [sql, binds]
|
39
|
+
result = nil
|
40
|
+
hit = false
|
41
|
+
|
42
|
+
@lock.synchronize do
|
43
|
+
if (result = @query_cache.delete(key))
|
44
|
+
hit = true
|
45
|
+
@query_cache[key] = result
|
27
46
|
else
|
28
|
-
query_cache[
|
47
|
+
result = @query_cache[key] = yield
|
48
|
+
@query_cache.shift if @query_cache_max_size && @query_cache.size > @query_cache_max_size
|
29
49
|
end
|
50
|
+
end
|
51
|
+
|
52
|
+
if hit
|
53
|
+
::ActiveSupport::Notifications.instrument(
|
54
|
+
"sql.active_record",
|
55
|
+
cache_notification_info(sql, name, binds)
|
56
|
+
)
|
57
|
+
end
|
58
|
+
|
30
59
|
result.dup
|
31
60
|
end
|
32
61
|
end
|
@@ -3,6 +3,19 @@
|
|
3
3
|
module Switchman
|
4
4
|
module ActiveRecord
|
5
5
|
module QueryMethods
|
6
|
+
# Use this class to prevent a value from getting transposed across shards
|
7
|
+
class NonTransposingValue < SimpleDelegator
|
8
|
+
def class
|
9
|
+
__getobj__.class
|
10
|
+
end
|
11
|
+
|
12
|
+
def is_a?(other)
|
13
|
+
return true if other == NonTransposingValue
|
14
|
+
|
15
|
+
__getobj__.is_a?(other)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
6
19
|
# shard_value is one of:
|
7
20
|
# A shard
|
8
21
|
# An array or relation of shards
|
@@ -84,6 +97,14 @@ module Switchman
|
|
84
97
|
super(other.shard(primary_shard))
|
85
98
|
end
|
86
99
|
|
100
|
+
# use a temp variable so that the new where clause is built before self.where_clause is read,
|
101
|
+
# since build_where_clause might mutate self.where_clause
|
102
|
+
def where!(opts, *rest)
|
103
|
+
new_clause = build_where_clause(opts, rest)
|
104
|
+
self.where_clause += new_clause
|
105
|
+
self
|
106
|
+
end
|
107
|
+
|
87
108
|
protected
|
88
109
|
|
89
110
|
def remove_nonlocal_primary_keys!
|
@@ -93,7 +114,7 @@ module Switchman
|
|
93
114
|
predicate.left.relation.klass == klass &&
|
94
115
|
(predicate.is_a?(::Arel::Nodes::Equality) || predicate.is_a?(::Arel::Nodes::HomogeneousIn))
|
95
116
|
|
96
|
-
value.is_a?(Integer) && value > Shard::IDS_PER_SHARD ? [] : value
|
117
|
+
(value.is_a?(Integer) && value > Shard::IDS_PER_SHARD) ? [] : value
|
97
118
|
end
|
98
119
|
self
|
99
120
|
end
|
@@ -104,7 +125,9 @@ module Switchman
|
|
104
125
|
return unless klass.integral_id?
|
105
126
|
|
106
127
|
primary_key = predicates.detect do |predicate|
|
107
|
-
(predicate.is_a?(::Arel::Nodes::
|
128
|
+
(predicate.is_a?(::Arel::Nodes::Equality) ||
|
129
|
+
predicate.is_a?(::Arel::Nodes::In) ||
|
130
|
+
predicate.is_a?(::Arel::Nodes::HomogeneousIn)) &&
|
108
131
|
predicate.left.is_a?(::Arel::Attributes::Attribute) &&
|
109
132
|
predicate.left.relation.is_a?(::Arel::Table) && predicate.left.relation.klass == klass &&
|
110
133
|
klass.primary_key == predicate.left.name
|
@@ -167,19 +190,19 @@ module Switchman
|
|
167
190
|
end
|
168
191
|
|
169
192
|
def sharded_foreign_key?(relation, column)
|
170
|
-
models_for_table(relation.
|
193
|
+
models_for_table(relation.name).any? { |m| m.sharded_column?(column) }
|
171
194
|
end
|
172
195
|
|
173
196
|
def sharded_primary_key?(relation, column)
|
174
197
|
column = column.to_s
|
175
|
-
return column ==
|
198
|
+
return column == "id" if relation.klass == ::ActiveRecord::Base
|
176
199
|
|
177
200
|
relation.klass.primary_key == column && relation.klass.integral_id?
|
178
201
|
end
|
179
202
|
|
180
203
|
def source_shard_for_foreign_key(relation, column)
|
181
204
|
reflection = nil
|
182
|
-
models_for_table(relation.
|
205
|
+
models_for_table(relation.name).each do |model|
|
183
206
|
reflection = model.send(:reflection_for_integer_attribute, column)
|
184
207
|
break if reflection
|
185
208
|
end
|
@@ -199,12 +222,11 @@ module Switchman
|
|
199
222
|
|
200
223
|
case opts
|
201
224
|
when String, Array
|
202
|
-
values = Hash === rest.first ? rest.first.values : rest
|
225
|
+
values = (Hash === rest.first) ? rest.first.values : rest
|
203
226
|
|
204
|
-
values.grep(ActiveRecord::Relation)
|
205
|
-
|
206
|
-
|
207
|
-
rel.shard!(primary_shard) if rel.shard_source_value == :implicit && rel.primary_shard != primary_shard
|
227
|
+
if shard_source_value != :explicit && values.grep(ActiveRecord::Relation).first
|
228
|
+
raise "Sub-queries are not allowed as simple substitutions; " \
|
229
|
+
"please build your relation with more structured methods so that Switchman is able to introspect it."
|
208
230
|
end
|
209
231
|
|
210
232
|
super
|
@@ -254,9 +276,10 @@ module Switchman
|
|
254
276
|
send(clause_setter, new_clause)
|
255
277
|
end
|
256
278
|
|
257
|
-
def each_transposable_predicate(predicates
|
279
|
+
def each_transposable_predicate(predicates, &block)
|
258
280
|
each_predicate(predicates) do |predicate|
|
259
|
-
|
281
|
+
case predicate
|
282
|
+
when ::Arel::Nodes::Grouping
|
260
283
|
next predicate unless predicate.expr.is_a?(::Arel::Nodes::Or)
|
261
284
|
|
262
285
|
or_expr = predicate.expr
|
@@ -267,6 +290,40 @@ module Switchman
|
|
267
290
|
next predicate if new_left == old_left && new_right == old_right
|
268
291
|
|
269
292
|
next predicate.class.new predicate.expr.class.new(new_left, new_right)
|
293
|
+
when ::Arel::Nodes::SelectStatement
|
294
|
+
new_cores = predicate.cores.map do |core|
|
295
|
+
next core unless core.is_a?(::Arel::Nodes::SelectCore) # just in case something weird is going on
|
296
|
+
|
297
|
+
new_wheres = each_transposable_predicate(core.wheres, &block)
|
298
|
+
new_havings = each_transposable_predicate(core.havings, &block)
|
299
|
+
|
300
|
+
next core if core.wheres == new_wheres && core.havings == new_havings
|
301
|
+
|
302
|
+
new_core = core.clone
|
303
|
+
new_core.wheres = new_wheres
|
304
|
+
new_core.havings = new_havings
|
305
|
+
new_core
|
306
|
+
end
|
307
|
+
|
308
|
+
next predicate if predicate.cores == new_cores
|
309
|
+
|
310
|
+
new_node = predicate.clone
|
311
|
+
new_node.instance_variable_set(:@cores, new_cores)
|
312
|
+
next new_node
|
313
|
+
when ::Arel::Nodes::Not
|
314
|
+
old_value = predicate.expr
|
315
|
+
new_value = each_transposable_predicate([old_value], &block).first
|
316
|
+
|
317
|
+
next predicate if old_value == new_value
|
318
|
+
|
319
|
+
next predicate.class.new(new_value)
|
320
|
+
when ::Arel::Nodes::Exists
|
321
|
+
old_value = predicate.expressions
|
322
|
+
new_value = each_transposable_predicate([old_value], &block).first
|
323
|
+
|
324
|
+
next predicate if old_value == new_value
|
325
|
+
|
326
|
+
next predicate.class.new(new_value)
|
270
327
|
end
|
271
328
|
|
272
329
|
next predicate unless predicate.is_a?(::Arel::Nodes::Binary) || predicate.is_a?(::Arel::Nodes::HomogeneousIn)
|
@@ -279,54 +336,56 @@ module Switchman
|
|
279
336
|
end
|
280
337
|
end
|
281
338
|
|
282
|
-
def each_transposable_predicate_value(predicates = nil)
|
339
|
+
def each_transposable_predicate_value(predicates = nil, &block)
|
283
340
|
each_transposable_predicate(predicates) do |predicate, relation, column, type|
|
284
|
-
each_transposable_predicate_value_cb(predicate) do |value|
|
341
|
+
each_transposable_predicate_value_cb(predicate, block) do |value|
|
285
342
|
yield(value, predicate, relation, column, type)
|
286
343
|
end
|
287
344
|
end
|
288
345
|
end
|
289
346
|
|
290
|
-
def each_transposable_predicate_value_cb(node, &block)
|
347
|
+
def each_transposable_predicate_value_cb(node, original_block, &block)
|
291
348
|
case node
|
292
349
|
when Array
|
293
|
-
node.
|
350
|
+
node.filter_map { |val| each_transposable_predicate_value_cb(val, original_block, &block).presence }
|
294
351
|
when ::ActiveModel::Attribute
|
295
352
|
old_value = node.value_before_type_cast
|
296
|
-
new_value = each_transposable_predicate_value_cb(old_value, &block)
|
353
|
+
new_value = each_transposable_predicate_value_cb(old_value, original_block, &block)
|
297
354
|
|
298
|
-
old_value == new_value ? node : node.class.new(node.name, new_value, node.type)
|
355
|
+
(old_value == new_value) ? node : node.class.new(node.name, new_value, node.type)
|
299
356
|
when ::Arel::Nodes::And
|
300
357
|
old_value = node.children
|
301
|
-
new_value = each_transposable_predicate_value_cb(old_value, &block)
|
358
|
+
new_value = each_transposable_predicate_value_cb(old_value, original_block, &block)
|
302
359
|
|
303
|
-
old_value == new_value ? node : node.class.new(new_value)
|
360
|
+
(old_value == new_value) ? node : node.class.new(new_value)
|
304
361
|
when ::Arel::Nodes::BindParam
|
305
362
|
old_value = node.value
|
306
|
-
new_value = each_transposable_predicate_value_cb(old_value, &block)
|
363
|
+
new_value = each_transposable_predicate_value_cb(old_value, original_block, &block)
|
307
364
|
|
308
|
-
old_value == new_value ? node : node.class.new(new_value)
|
365
|
+
(old_value == new_value) ? node : node.class.new(new_value)
|
309
366
|
when ::Arel::Nodes::Casted
|
310
367
|
old_value = node.value
|
311
|
-
new_value = each_transposable_predicate_value_cb(old_value, &block)
|
368
|
+
new_value = each_transposable_predicate_value_cb(old_value, original_block, &block)
|
312
369
|
|
313
|
-
old_value == new_value ? node : node.class.new(new_value, node.attribute)
|
370
|
+
(old_value == new_value) ? node : node.class.new(new_value, node.attribute)
|
314
371
|
when ::Arel::Nodes::HomogeneousIn
|
315
372
|
old_value = node.values
|
316
|
-
new_value = each_transposable_predicate_value_cb(old_value, &block)
|
373
|
+
new_value = each_transposable_predicate_value_cb(old_value, original_block, &block)
|
317
374
|
|
318
375
|
# switch to a regular In, so that Relation::WhereClause#contradiction? knows about it
|
319
376
|
if new_value.empty?
|
320
|
-
klass = node.type == :in ? ::Arel::Nodes::In : ::Arel::Nodes::NotIn
|
377
|
+
klass = (node.type == :in) ? ::Arel::Nodes::In : ::Arel::Nodes::NotIn
|
321
378
|
klass.new(node.attribute, new_value)
|
322
379
|
else
|
323
|
-
old_value == new_value ? node : node.class.new(new_value, node.attribute, node.type)
|
380
|
+
(old_value == new_value) ? node : node.class.new(new_value, node.attribute, node.type)
|
324
381
|
end
|
325
382
|
when ::Arel::Nodes::Binary
|
326
383
|
old_value = node.right
|
327
|
-
new_value = each_transposable_predicate_value_cb(old_value, &block)
|
384
|
+
new_value = each_transposable_predicate_value_cb(old_value, original_block, &block)
|
328
385
|
|
329
|
-
old_value == new_value ? node : node.class.new(node.left, new_value)
|
386
|
+
(old_value == new_value) ? node : node.class.new(node.left, new_value)
|
387
|
+
when ::Arel::Nodes::SelectStatement
|
388
|
+
each_transposable_predicate_value([node], &original_block).first
|
330
389
|
else
|
331
390
|
yield(node)
|
332
391
|
end
|
@@ -343,6 +402,8 @@ module Switchman
|
|
343
402
|
Shard.current(klass.connection_class_for_self)
|
344
403
|
elsif type == :foreign
|
345
404
|
source_shard_for_foreign_key(relation, column)
|
405
|
+
else
|
406
|
+
primary_shard
|
346
407
|
end
|
347
408
|
|
348
409
|
transpose_predicate_value(value, current_source_shard, target_shard, type)
|
@@ -350,7 +411,9 @@ module Switchman
|
|
350
411
|
end
|
351
412
|
|
352
413
|
def transpose_predicate_value(value, current_shard, target_shard, attribute_type)
|
353
|
-
if value.is_a?(
|
414
|
+
if value.is_a?(NonTransposingValue)
|
415
|
+
value
|
416
|
+
elsif value.is_a?(::ActiveRecord::StatementCache::Substitute)
|
354
417
|
value.sharded = true # mark for transposition later
|
355
418
|
value.primary = true if attribute_type == :primary
|
356
419
|
value
|