switchman 3.0.5 → 4.0.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/Rakefile +16 -15
- data/db/migrate/20180828183945_add_default_shard_index.rb +2 -2
- data/db/migrate/20180828192111_add_timestamps_to_shards.rb +1 -1
- data/db/migrate/20190114212900_add_unique_name_indexes.rb +10 -4
- data/lib/switchman/action_controller/caching.rb +2 -2
- data/lib/switchman/active_record/abstract_adapter.rb +6 -15
- data/lib/switchman/active_record/associations.rb +331 -0
- data/lib/switchman/active_record/attribute_methods.rb +182 -77
- data/lib/switchman/active_record/base.rb +249 -46
- data/lib/switchman/active_record/calculations.rb +98 -44
- data/lib/switchman/active_record/connection_handler.rb +18 -0
- data/lib/switchman/active_record/connection_pool.rb +27 -28
- data/lib/switchman/active_record/database_configurations.rb +44 -6
- data/lib/switchman/active_record/finder_methods.rb +46 -16
- data/lib/switchman/active_record/log_subscriber.rb +11 -5
- data/lib/switchman/active_record/migration.rb +52 -5
- data/lib/switchman/active_record/model_schema.rb +1 -1
- data/lib/switchman/active_record/pending_migration_connection.rb +17 -0
- data/lib/switchman/active_record/persistence.rb +37 -2
- data/lib/switchman/active_record/postgresql_adapter.rb +12 -11
- 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 +202 -136
- data/lib/switchman/active_record/reflection.rb +1 -1
- data/lib/switchman/active_record/relation.rb +40 -28
- data/lib/switchman/active_record/spawn_methods.rb +2 -2
- data/lib/switchman/active_record/statement_cache.rb +11 -7
- data/lib/switchman/active_record/table_definition.rb +1 -1
- data/lib/switchman/active_record/tasks/database_tasks.rb +6 -1
- data/lib/switchman/active_record/test_fixtures.rb +53 -0
- data/lib/switchman/active_support/cache.rb +25 -4
- data/lib/switchman/arel.rb +45 -7
- data/lib/switchman/call_super.rb +2 -2
- data/lib/switchman/database_server.rb +123 -79
- data/lib/switchman/default_shard.rb +14 -5
- data/lib/switchman/engine.rb +79 -131
- data/lib/switchman/environment.rb +2 -2
- data/lib/switchman/errors.rb +17 -2
- data/lib/switchman/guard_rail/relation.rb +7 -10
- data/lib/switchman/guard_rail.rb +5 -0
- data/lib/switchman/parallel.rb +68 -0
- data/lib/switchman/r_spec_helper.rb +17 -28
- data/lib/switchman/rails.rb +1 -4
- data/{app/models → lib}/switchman/shard.rb +226 -241
- data/lib/switchman/sharded_instrumenter.rb +3 -3
- data/lib/switchman/shared_schema_cache.rb +11 -0
- data/lib/switchman/standard_error.rb +15 -12
- data/lib/switchman/test_helper.rb +2 -2
- data/{app/models → lib}/switchman/unsharded_record.rb +1 -1
- data/lib/switchman/version.rb +1 -1
- data/lib/switchman.rb +44 -12
- data/lib/tasks/switchman.rake +101 -54
- metadata +50 -58
- data/lib/switchman/active_record/association.rb +0 -206
- data/lib/switchman/open4.rb +0 -80
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'switchman/errors'
|
|
4
|
-
|
|
5
3
|
module Switchman
|
|
6
4
|
module ActiveRecord
|
|
7
5
|
module ConnectionPool
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
if ::Rails.version < "7.1"
|
|
7
|
+
def get_schema_cache(connection)
|
|
8
|
+
self.schema_cache ||= SharedSchemaCache.get_schema_cache(connection)
|
|
9
|
+
self.schema_cache.connection = connection
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
unless (shard_stack = Thread.current.thread_variable_get(tls_key))
|
|
14
|
-
shard_stack = Concurrent::Array.new
|
|
15
|
-
Thread.current.thread_variable_set(tls_key, shard_stack)
|
|
11
|
+
self.schema_cache
|
|
16
12
|
end
|
|
17
|
-
|
|
13
|
+
|
|
14
|
+
# rubocop:disable Naming/AccessorMethodName override method
|
|
15
|
+
def set_schema_cache(cache)
|
|
16
|
+
schema_cache = get_schema_cache(cache.connection)
|
|
17
|
+
|
|
18
|
+
cache.instance_variables.each do |x|
|
|
19
|
+
schema_cache.instance_variable_set(x, cache.instance_variable_get(x))
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
# rubocop:enable Naming/AccessorMethodName override method
|
|
18
23
|
end
|
|
19
24
|
|
|
20
25
|
def default_schema
|
|
@@ -26,15 +31,15 @@ module Switchman
|
|
|
26
31
|
|
|
27
32
|
def checkout_new_connection
|
|
28
33
|
conn = super
|
|
29
|
-
conn.shard =
|
|
34
|
+
conn.shard = current_shard
|
|
30
35
|
conn
|
|
31
36
|
end
|
|
32
37
|
|
|
33
38
|
def connection(switch_shard: true)
|
|
34
39
|
conn = super()
|
|
35
|
-
raise NonExistentShardError if
|
|
40
|
+
raise Errors::NonExistentShardError if current_shard.new_record?
|
|
36
41
|
|
|
37
|
-
switch_database(conn) if conn.shard !=
|
|
42
|
+
switch_database(conn) if conn.shard != current_shard && switch_shard
|
|
38
43
|
conn
|
|
39
44
|
end
|
|
40
45
|
|
|
@@ -44,28 +49,22 @@ module Switchman
|
|
|
44
49
|
flush
|
|
45
50
|
end
|
|
46
51
|
|
|
47
|
-
def remove_shard!(shard)
|
|
48
|
-
synchronize do
|
|
49
|
-
# The shard might be currently active, so we need to update our own shard
|
|
50
|
-
self.shard = Shard.default if self.shard == shard
|
|
51
|
-
# Update out any connections that may be using this shard
|
|
52
|
-
@connections.each do |conn|
|
|
53
|
-
# This will also update the connection's shard to the default shard
|
|
54
|
-
switch_database(conn) if conn.shard == shard
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
|
|
59
52
|
def switch_database(conn)
|
|
60
|
-
|
|
53
|
+
if !@schemas && conn.adapter_name == "PostgreSQL" && !current_shard.database_server.config[:shard_name]
|
|
54
|
+
@schemas = conn.current_schemas
|
|
55
|
+
end
|
|
61
56
|
|
|
62
|
-
conn.shard =
|
|
57
|
+
conn.shard = current_shard
|
|
63
58
|
end
|
|
64
59
|
|
|
65
60
|
private
|
|
66
61
|
|
|
62
|
+
def current_shard
|
|
63
|
+
connection_class.current_switchman_shard
|
|
64
|
+
end
|
|
65
|
+
|
|
67
66
|
def tls_key
|
|
68
|
-
"#{object_id}_shard"
|
|
67
|
+
:"#{object_id}_shard"
|
|
69
68
|
end
|
|
70
69
|
end
|
|
71
70
|
end
|
|
@@ -3,6 +3,32 @@
|
|
|
3
3
|
module Switchman
|
|
4
4
|
module ActiveRecord
|
|
5
5
|
module DatabaseConfigurations
|
|
6
|
+
# key difference: For each env name, ensure only one writable config is returned
|
|
7
|
+
# since all should point to the same data, even if multiple are writable
|
|
8
|
+
# (Picks 'primary' since it is guaranteed to exist and switchman handles activating
|
|
9
|
+
# deploy through other means)
|
|
10
|
+
if ::Rails.version < "7.1"
|
|
11
|
+
def configs_for(include_replicas: false, name: nil, **)
|
|
12
|
+
res = super
|
|
13
|
+
if name && !include_replicas
|
|
14
|
+
return nil unless name.end_with?("primary")
|
|
15
|
+
elsif !include_replicas
|
|
16
|
+
return res.select { |config| config.name.end_with?("primary") }
|
|
17
|
+
end
|
|
18
|
+
res
|
|
19
|
+
end
|
|
20
|
+
else
|
|
21
|
+
def configs_for(include_hidden: false, name: nil, **)
|
|
22
|
+
res = super
|
|
23
|
+
if name && !include_hidden
|
|
24
|
+
return nil unless name.end_with?("primary")
|
|
25
|
+
elsif !include_hidden
|
|
26
|
+
return res.select { |config| config.name.end_with?("primary") }
|
|
27
|
+
end
|
|
28
|
+
res
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
6
32
|
private
|
|
7
33
|
|
|
8
34
|
# key difference: assumes a hybrid two-tier structure; each third tier
|
|
@@ -13,19 +39,31 @@ module Switchman
|
|
|
13
39
|
return configs if configs.is_a?(Array)
|
|
14
40
|
|
|
15
41
|
db_configs = configs.flat_map do |env_name, config|
|
|
16
|
-
|
|
17
|
-
|
|
42
|
+
if config.is_a?(Hash)
|
|
43
|
+
# It would be nice to do the auto-fallback that we want here, but we haven't
|
|
44
|
+
# actually done that for years (or maybe ever) and it will be a big lift to get working
|
|
45
|
+
roles = config.keys.select do |k|
|
|
46
|
+
config[k].is_a?(Hash) || (config[k].is_a?(Array) && config[k].all?(Hash))
|
|
47
|
+
end
|
|
48
|
+
base_config = config.except(*roles)
|
|
49
|
+
else
|
|
50
|
+
base_config = config
|
|
51
|
+
roles = []
|
|
52
|
+
end
|
|
18
53
|
|
|
19
54
|
name = "#{env_name}/primary"
|
|
20
|
-
name =
|
|
55
|
+
name = "primary" if env_name == default_env
|
|
21
56
|
base_db = build_db_config_from_raw_config(env_name, name, base_config)
|
|
22
57
|
[base_db] + roles.map do |role|
|
|
23
|
-
build_db_config_from_raw_config(
|
|
24
|
-
|
|
58
|
+
build_db_config_from_raw_config(
|
|
59
|
+
env_name,
|
|
60
|
+
"#{env_name}/#{role}",
|
|
61
|
+
base_config.merge(config[role].is_a?(Array) ? config[role].first : config[role])
|
|
62
|
+
)
|
|
25
63
|
end
|
|
26
64
|
end
|
|
27
65
|
|
|
28
|
-
db_configs << environment_url_config(default_env,
|
|
66
|
+
db_configs << environment_url_config(default_env, "primary", {}) unless db_configs.find(&:for_current_env?)
|
|
29
67
|
|
|
30
68
|
merge_db_environment_variables(default_env, db_configs.compact)
|
|
31
69
|
end
|
|
@@ -7,7 +7,7 @@ module Switchman
|
|
|
7
7
|
return super(id) unless klass.integral_id?
|
|
8
8
|
|
|
9
9
|
if shard_source_value != :implicit
|
|
10
|
-
current_shard = Shard.current(klass.
|
|
10
|
+
current_shard = Shard.current(klass.connection_class_for_self)
|
|
11
11
|
result = activate do |relation, shard|
|
|
12
12
|
current_id = Shard.relative_id_for(id, current_shard, shard)
|
|
13
13
|
# current_id will be nil for non-integral id
|
|
@@ -33,7 +33,7 @@ module Switchman
|
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
def find_some_ordered(ids)
|
|
36
|
-
current_shard = Shard.current(klass.
|
|
36
|
+
current_shard = Shard.current(klass.connection_class_for_self)
|
|
37
37
|
ids = ids.map { |id| Shard.relative_id_for(id, current_shard, current_shard) }
|
|
38
38
|
super(ids)
|
|
39
39
|
end
|
|
@@ -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,16 +14,27 @@ 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
|
|
21
23
|
|
|
22
24
|
module Migrator
|
|
23
|
-
# significant change:
|
|
25
|
+
# significant change: use the shard name instead of the database name
|
|
26
|
+
# in the lock id. Especially if you're going through pgbouncer, the
|
|
27
|
+
# database name you're accessing may not be consistent
|
|
24
28
|
def generate_migrator_advisory_lock_id
|
|
25
|
-
|
|
26
|
-
::ActiveRecord::Migrator::MIGRATOR_SALT *
|
|
29
|
+
db_name_hash = Zlib.crc32(Shard.current.name)
|
|
30
|
+
shard_name_hash = ::ActiveRecord::Migrator::MIGRATOR_SALT * db_name_hash
|
|
31
|
+
# Store in internalmetadata to allow other tools to be able to lock out migrations
|
|
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
|
|
37
|
+
shard_name_hash
|
|
27
38
|
end
|
|
28
39
|
|
|
29
40
|
# significant change: strip out prefer_secondary from config
|
|
@@ -39,13 +50,49 @@ module Switchman
|
|
|
39
50
|
end
|
|
40
51
|
|
|
41
52
|
module MigrationContext
|
|
53
|
+
def migrate(...)
|
|
54
|
+
connection = ::ActiveRecord::Base.connection
|
|
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
|
|
62
|
+
|
|
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
|
|
74
|
+
|
|
75
|
+
begin
|
|
76
|
+
super(...)
|
|
77
|
+
ensure
|
|
78
|
+
schema_cache_holder.set_schema_cache(previous_schema_cache)
|
|
79
|
+
reset_column_information
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
42
83
|
def migrations
|
|
43
84
|
return @migrations if instance_variable_defined?(:@migrations)
|
|
44
85
|
|
|
45
86
|
migrations_cache = Thread.current[:migrations_cache] ||= {}
|
|
46
|
-
key = Digest::MD5.hexdigest(migration_files.sort.join(
|
|
87
|
+
key = Digest::MD5.hexdigest(migration_files.sort.join(","))
|
|
47
88
|
@migrations = migrations_cache[key] ||= super
|
|
48
89
|
end
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
def reset_column_information
|
|
94
|
+
::ActiveRecord::Base.descendants.reject { |m| m <= UnshardedRecord }.each(&:reset_column_information)
|
|
95
|
+
end
|
|
49
96
|
end
|
|
50
97
|
end
|
|
51
98
|
end
|
|
@@ -6,7 +6,7 @@ module Switchman
|
|
|
6
6
|
module ClassMethods
|
|
7
7
|
def quoted_table_name
|
|
8
8
|
@quoted_table_name ||= {}
|
|
9
|
-
@quoted_table_name[Shard.current(
|
|
9
|
+
@quoted_table_name[Shard.current(connection_class_for_self).id] ||= connection.quote_table_name(table_name)
|
|
10
10
|
end
|
|
11
11
|
end
|
|
12
12
|
end
|
|
@@ -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,11 +5,46 @@ 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
|
-
|
|
8
|
+
writable_shadow_record_warning
|
|
9
|
+
shard.activate(self.class.connection_class_for_self) { super }
|
|
9
10
|
end
|
|
10
11
|
|
|
11
12
|
def update_columns(*)
|
|
12
|
-
|
|
13
|
+
writable_shadow_record_warning
|
|
14
|
+
shard.activate(self.class.connection_class_for_self) { super }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def delete
|
|
18
|
+
db = shard.database_server
|
|
19
|
+
db.unguard { super }
|
|
20
|
+
end
|
|
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
|
+
|
|
32
|
+
def reload(*)
|
|
33
|
+
res = super
|
|
34
|
+
# When a shadow record is reloaded the real record is returned. So
|
|
35
|
+
# we need to ensure the loaded_from_shard is set correctly after a reload.
|
|
36
|
+
@loaded_from_shard = @shard
|
|
37
|
+
if @readonly_from_shadow
|
|
38
|
+
@readonly_from_shadow = false
|
|
39
|
+
@readonly = false
|
|
40
|
+
end
|
|
41
|
+
res
|
|
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")
|
|
13
48
|
end
|
|
14
49
|
end
|
|
15
50
|
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 do |key, value|
|
|
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
|
|
|
@@ -32,12 +32,12 @@ module Switchman
|
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
# copy/paste; use quote_local_table_name
|
|
35
|
-
def drop_database(name)
|
|
35
|
+
def drop_database(name) # :nodoc:
|
|
36
36
|
execute "DROP DATABASE IF EXISTS #{quote_local_table_name(name)}"
|
|
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
|