switchman 3.4.2 → 3.6.7
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/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
|