switchman 1.5.21 → 2.1.5
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 +5 -5
- data/app/models/switchman/shard.rb +757 -11
- data/db/migrate/20130328212039_create_switchman_shards.rb +3 -1
- data/db/migrate/20130328224244_create_default_shard.rb +4 -2
- data/db/migrate/20161206323434_add_back_default_string_limits_switchman.rb +13 -0
- data/db/migrate/20180828183945_add_default_shard_index.rb +15 -0
- data/db/migrate/20180828192111_add_timestamps_to_shards.rb +17 -0
- data/db/migrate/20190114212900_add_unique_name_indexes.rb +9 -0
- data/lib/switchman/action_controller/caching.rb +2 -0
- data/lib/switchman/active_record/abstract_adapter.rb +14 -4
- data/lib/switchman/active_record/association.rb +64 -37
- data/lib/switchman/active_record/attribute_methods.rb +54 -22
- data/lib/switchman/active_record/base.rb +76 -31
- data/lib/switchman/active_record/batches.rb +3 -1
- data/lib/switchman/active_record/calculations.rb +17 -22
- data/lib/switchman/active_record/connection_handler.rb +88 -78
- data/lib/switchman/active_record/connection_pool.rb +28 -23
- data/lib/switchman/active_record/finder_methods.rb +37 -28
- data/lib/switchman/active_record/log_subscriber.rb +14 -19
- data/lib/switchman/active_record/migration.rb +80 -0
- data/lib/switchman/active_record/model_schema.rb +3 -1
- data/lib/switchman/active_record/persistence.rb +9 -1
- data/lib/switchman/active_record/postgresql_adapter.rb +170 -126
- data/lib/switchman/active_record/predicate_builder.rb +3 -1
- data/lib/switchman/active_record/query_cache.rb +22 -87
- data/lib/switchman/active_record/query_methods.rb +139 -125
- data/lib/switchman/active_record/reflection.rb +42 -14
- data/lib/switchman/active_record/relation.rb +108 -33
- data/lib/switchman/active_record/spawn_methods.rb +2 -0
- data/lib/switchman/active_record/statement_cache.rb +44 -52
- data/lib/switchman/active_record/table_definition.rb +4 -2
- data/lib/switchman/active_record/type_caster.rb +2 -0
- data/lib/switchman/active_record/where_clause_factory.rb +5 -2
- data/lib/switchman/active_support/cache.rb +18 -0
- data/lib/switchman/arel.rb +8 -25
- data/lib/switchman/call_super.rb +19 -0
- data/lib/switchman/connection_pool_proxy.rb +70 -24
- data/lib/switchman/database_server.rb +69 -59
- data/lib/switchman/default_shard.rb +3 -0
- data/lib/switchman/engine.rb +44 -41
- data/lib/switchman/environment.rb +2 -0
- data/lib/switchman/errors.rb +2 -0
- data/lib/switchman/{shackles → guard_rail}/relation.rb +7 -5
- data/lib/switchman/{shackles.rb → guard_rail.rb} +6 -4
- data/lib/switchman/open4.rb +2 -0
- data/lib/switchman/r_spec_helper.rb +14 -8
- data/lib/switchman/rails.rb +2 -0
- data/lib/switchman/schema_cache.rb +17 -0
- data/lib/switchman/sharded_instrumenter.rb +4 -2
- data/lib/switchman/standard_error.rb +4 -2
- data/lib/switchman/test_helper.rb +7 -10
- data/lib/switchman/version.rb +3 -1
- data/lib/switchman.rb +5 -1
- data/lib/tasks/switchman.rake +53 -72
- metadata +84 -38
- data/app/models/switchman/shard_internal.rb +0 -692
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Switchman
|
|
4
|
+
module ActiveRecord
|
|
5
|
+
module Migration
|
|
6
|
+
module Compatibility
|
|
7
|
+
module V5_0
|
|
8
|
+
def create_table(*args, **options)
|
|
9
|
+
unless options.key?(:id)
|
|
10
|
+
options[:id] = :bigserial
|
|
11
|
+
end
|
|
12
|
+
if block_given?
|
|
13
|
+
super do |td|
|
|
14
|
+
yield td
|
|
15
|
+
end
|
|
16
|
+
else
|
|
17
|
+
super
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def connection
|
|
24
|
+
conn = super
|
|
25
|
+
if conn.shard != ::ActiveRecord::Base.connection_pool.current_pool.shard
|
|
26
|
+
::ActiveRecord::Base.connection_pool.current_pool.switch_database(conn)
|
|
27
|
+
end
|
|
28
|
+
conn
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
module Migrator
|
|
33
|
+
# significant change: hash shard id, not database name
|
|
34
|
+
def generate_migrator_advisory_lock_id
|
|
35
|
+
shard_name_hash = Zlib.crc32(Shard.current.name)
|
|
36
|
+
::ActiveRecord::Migrator::MIGRATOR_SALT * shard_name_hash
|
|
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
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
module MigrationContext
|
|
72
|
+
def migrations
|
|
73
|
+
return @migrations if instance_variable_defined?(:@migrations)
|
|
74
|
+
migrations_cache = Thread.current[:migrations_cache] ||= {}
|
|
75
|
+
key = Digest::MD5.hexdigest(migration_files.sort.join(','))
|
|
76
|
+
@migrations = migrations_cache[key] ||= super
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Switchman
|
|
2
4
|
module ActiveRecord
|
|
3
5
|
module ModelSchema
|
|
4
6
|
module ClassMethods
|
|
5
7
|
def quoted_table_name
|
|
6
8
|
@quoted_table_name ||= {}
|
|
7
|
-
@quoted_table_name[Shard.current.id] ||= connection.quote_table_name(table_name)
|
|
9
|
+
@quoted_table_name[Shard.current(shard_category).id] ||= connection.quote_table_name(table_name)
|
|
8
10
|
end
|
|
9
11
|
end
|
|
10
12
|
end
|
|
@@ -1,10 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Switchman
|
|
2
4
|
module ActiveRecord
|
|
3
5
|
module Persistence
|
|
4
6
|
# touch reads the id attribute directly, so it's not relative to the current shard
|
|
5
|
-
def touch(
|
|
7
|
+
def touch(*, **)
|
|
6
8
|
shard.activate(self.class.shard_category) { super }
|
|
7
9
|
end
|
|
10
|
+
|
|
11
|
+
if ::Rails.version >= '5.2'
|
|
12
|
+
def update_columns(*)
|
|
13
|
+
shard.activate(self.class.shard_category) { super }
|
|
14
|
+
end
|
|
15
|
+
end
|
|
8
16
|
end
|
|
9
17
|
end
|
|
10
18
|
end
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Switchman
|
|
2
4
|
module ActiveRecord
|
|
3
5
|
module PostgreSQLAdapter
|
|
4
|
-
def self.prepended(klass)
|
|
5
|
-
klass::NATIVE_DATABASE_TYPES[:primary_key] = "bigserial primary key".freeze
|
|
6
|
-
end
|
|
7
|
-
|
|
8
6
|
# copy/paste; use quote_local_table_name
|
|
9
7
|
def create_database(name, options = {})
|
|
10
8
|
options = { encoding: 'utf8' }.merge!(options.symbolize_keys)
|
|
@@ -42,149 +40,119 @@ module Switchman
|
|
|
42
40
|
select_values("SELECT * FROM unnest(current_schemas(false))")
|
|
43
41
|
end
|
|
44
42
|
|
|
45
|
-
def
|
|
46
|
-
|
|
43
|
+
def extract_schema_qualified_name(string)
|
|
44
|
+
name = ::ActiveRecord::ConnectionAdapters::PostgreSQL::Utils.extract_schema_qualified_name(string.to_s)
|
|
45
|
+
if string && !name.schema
|
|
46
|
+
name.instance_variable_set(:@schema, shard.name)
|
|
47
|
+
end
|
|
48
|
+
[name.schema, name.identifier]
|
|
47
49
|
end
|
|
48
50
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
|
57
68
|
end
|
|
58
69
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
binds = [[nil, table]]
|
|
66
|
-
binds << [nil, schema] if schema
|
|
67
|
-
|
|
68
|
-
exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
|
|
69
|
-
SELECT COUNT(*)
|
|
70
|
-
FROM pg_class c
|
|
71
|
-
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
72
|
-
WHERE c.relkind in ('v','r')
|
|
73
|
-
AND c.relname = '#{table.gsub(/(^"|"$)/,'')}'
|
|
74
|
-
AND n.nspname = #{schema ? "'#{schema}'" : 'ANY (current_schemas(false))'}
|
|
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}'
|
|
75
76
|
SQL
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def view_exists?(name)
|
|
80
|
+
name = ::ActiveRecord::ConnectionAdapters::PostgreSQL::Utils.extract_schema_qualified_name(name.to_s)
|
|
78
81
|
return false unless name.identifier
|
|
79
|
-
if !name.schema
|
|
82
|
+
if !name.schema
|
|
80
83
|
name.instance_variable_set(:@schema, shard.name)
|
|
81
84
|
end
|
|
82
85
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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}'
|
|
90
93
|
SQL
|
|
91
94
|
end
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def indexes(table_name)
|
|
95
|
-
schema = shard.name if use_qualified_names?
|
|
96
|
-
|
|
97
|
-
result = query(<<-SQL, 'SCHEMA')
|
|
98
|
-
SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
|
|
99
|
-
FROM pg_class t
|
|
100
|
-
INNER JOIN pg_index d ON t.oid = d.indrelid
|
|
101
|
-
INNER JOIN pg_class i ON d.indexrelid = i.oid
|
|
102
|
-
WHERE i.relkind = 'i'
|
|
103
|
-
AND d.indisprimary = 'f'
|
|
104
|
-
AND t.relname = '#{table_name}'
|
|
105
|
-
AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = #{schema ? "'#{schema}'" : 'ANY (current_schemas(false))'} )
|
|
106
|
-
ORDER BY i.relname
|
|
107
|
-
SQL
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
result.map do |row|
|
|
111
|
-
index_name = row[0]
|
|
112
|
-
unique = 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
95
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
::ActiveRecord::ConnectionAdapters::IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where, nil, using)
|
|
134
|
-
end
|
|
135
|
-
end.compact
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
def index_name_exists?(table_name, index_name, default)
|
|
139
|
-
schema = shard.name if use_qualified_names?
|
|
140
|
-
|
|
141
|
-
exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
|
|
142
|
-
SELECT COUNT(*)
|
|
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
|
|
143
99
|
FROM pg_class t
|
|
144
100
|
INNER JOIN pg_index d ON t.oid = d.indrelid
|
|
145
101
|
INNER JOIN pg_class i ON d.indexrelid = i.oid
|
|
146
102
|
WHERE i.relkind = 'i'
|
|
147
|
-
AND
|
|
103
|
+
AND d.indisprimary = 'f'
|
|
148
104
|
AND t.relname = '#{table_name}'
|
|
149
|
-
AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname =
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
def quote_local_table_name(name)
|
|
154
|
-
# postgres quotes tables and columns the same; just pass through
|
|
155
|
-
# (differs from quote_table_name below by no logic to explicitly
|
|
156
|
-
# qualify the table)
|
|
157
|
-
quote_column_name(name)
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
def quote_table_name name
|
|
161
|
-
if ::Rails.version < '4.2'.freeze
|
|
162
|
-
schema, name_part = extract_pg_identifier_from_name(name.to_s)
|
|
105
|
+
AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = '#{shard.name}' )
|
|
106
|
+
ORDER BY i.relname
|
|
107
|
+
SQL
|
|
163
108
|
|
|
164
|
-
if !name_part && use_qualified_names? && shard.name
|
|
165
|
-
schema, name_part = shard.name, schema
|
|
166
|
-
end
|
|
167
109
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
|
175
141
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
|
181
153
|
end
|
|
182
|
-
end
|
|
183
154
|
|
|
184
|
-
if ::Rails.version >= '4.2'
|
|
185
155
|
def foreign_keys(table_name)
|
|
186
|
-
schema = shard.name if use_qualified_names?
|
|
187
|
-
|
|
188
156
|
# mostly copy-pasted from AR - only change is to the nspname condition for qualified names support
|
|
189
157
|
fk_info = select_all <<-SQL.strip_heredoc
|
|
190
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
|
|
@@ -196,7 +164,7 @@ module Switchman
|
|
|
196
164
|
JOIN pg_namespace t3 ON c.connamespace = t3.oid
|
|
197
165
|
WHERE c.contype = 'f'
|
|
198
166
|
AND t1.relname = #{quote(table_name)}
|
|
199
|
-
AND t3.nspname =
|
|
167
|
+
AND t3.nspname = '#{shard.name}'
|
|
200
168
|
ORDER BY c.conname
|
|
201
169
|
SQL
|
|
202
170
|
|
|
@@ -210,9 +178,85 @@ module Switchman
|
|
|
210
178
|
options[:on_delete] = extract_foreign_key_action(row['on_delete'])
|
|
211
179
|
options[:on_update] = extract_foreign_key_action(row['on_update'])
|
|
212
180
|
|
|
213
|
-
|
|
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)
|
|
214
189
|
end
|
|
215
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
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def quote_local_table_name(name)
|
|
203
|
+
# postgres quotes tables and columns the same; just pass through
|
|
204
|
+
# (differs from quote_table_name_with_shard below by no logic to
|
|
205
|
+
# explicitly qualify the table)
|
|
206
|
+
quote_column_name(name)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def quote_table_name(name)
|
|
210
|
+
return quote_local_table_name(name) if @use_local_table_name
|
|
211
|
+
name = ::ActiveRecord::ConnectionAdapters::PostgreSQL::Utils.extract_schema_qualified_name(name.to_s)
|
|
212
|
+
if !name.schema
|
|
213
|
+
name.instance_variable_set(:@schema, shard.name)
|
|
214
|
+
end
|
|
215
|
+
name.quoted
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def with_global_table_name(&block)
|
|
219
|
+
with_local_table_name(false, &block)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def with_local_table_name(enable = true)
|
|
223
|
+
old_value = @use_local_table_name
|
|
224
|
+
@use_local_table_name = enable
|
|
225
|
+
yield
|
|
226
|
+
ensure
|
|
227
|
+
@use_local_table_name = old_value
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def add_index_options(_table_name, _column_name, **)
|
|
231
|
+
index_name, index_type, index_columns, index_options, algorithm, using = super
|
|
232
|
+
algorithm = nil if DatabaseServer.creating_new_shard && algorithm == "CONCURRENTLY"
|
|
233
|
+
[index_name, index_type, index_columns, index_options, algorithm, using]
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def rename_table(table_name, new_name)
|
|
237
|
+
clear_cache!
|
|
238
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_local_table_name(new_name)}"
|
|
239
|
+
pk, seq = pk_and_sequence_for(new_name)
|
|
240
|
+
if pk
|
|
241
|
+
idx = "#{table_name}_pkey"
|
|
242
|
+
new_idx = "#{new_name}_pkey"
|
|
243
|
+
execute "ALTER INDEX #{quote_table_name(idx)} RENAME TO #{quote_local_table_name(new_idx)}"
|
|
244
|
+
if seq && seq.identifier == "#{table_name}_#{pk}_seq"
|
|
245
|
+
new_seq = "#{new_name}_#{pk}_seq"
|
|
246
|
+
execute "ALTER TABLE #{seq.quoted} RENAME TO #{quote_local_table_name(new_seq)}"
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
rename_table_indexes(table_name, new_name)
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def rename_index(table_name, old_name, new_name)
|
|
253
|
+
validate_index_length!(table_name, new_name)
|
|
254
|
+
|
|
255
|
+
execute "ALTER INDEX #{quote_table_name(old_name)} RENAME TO #{quote_local_table_name(new_name)}"
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def columns(*)
|
|
259
|
+
with_local_table_name(false) { super }
|
|
216
260
|
end
|
|
217
261
|
end
|
|
218
262
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Switchman
|
|
2
4
|
module ActiveRecord
|
|
3
5
|
module PredicateBuilder
|
|
@@ -13,7 +15,7 @@ module Switchman
|
|
|
13
15
|
def convert_to_id(value)
|
|
14
16
|
case value
|
|
15
17
|
when ::ActiveRecord::Base
|
|
16
|
-
value.
|
|
18
|
+
value.id
|
|
17
19
|
else
|
|
18
20
|
super
|
|
19
21
|
end
|
|
@@ -1,99 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Switchman
|
|
2
4
|
module ActiveRecord
|
|
3
5
|
module QueryCache
|
|
4
|
-
# thread local accessors to replace @query_cache_enabled
|
|
5
|
-
def query_cache
|
|
6
|
-
thread_cache = Thread.current[:query_cache] ||= {}
|
|
7
|
-
thread_cache[self.object_id] ||= Hash.new { |h,sql| h[sql] = {} }
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
def query_cache_enabled
|
|
11
|
-
Thread.current[:query_cache_enabled]
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def query_cache_enabled=(value)
|
|
15
|
-
Thread.current[:query_cache_enabled] = value
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
# basically wholesale repeat of the methods from the original (see
|
|
19
|
-
# https://github.com/rails/rails/blob/master/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb),
|
|
20
|
-
# but with self.query_cache_enabled and self.query_cache_enabled= instead
|
|
21
|
-
# of @query_cache_enabled.
|
|
22
|
-
|
|
23
|
-
def enable_query_cache!
|
|
24
|
-
self.query_cache_enabled = true
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def disable_query_cache!
|
|
28
|
-
self.query_cache_enabled = false
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def cache
|
|
32
|
-
old, self.query_cache_enabled = query_cache_enabled, true
|
|
33
|
-
yield
|
|
34
|
-
ensure
|
|
35
|
-
self.query_cache_enabled = old
|
|
36
|
-
clear_query_cache unless self.query_cache_enabled
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def uncached
|
|
40
|
-
old, self.query_cache_enabled = query_cache_enabled, false
|
|
41
|
-
yield
|
|
42
|
-
ensure
|
|
43
|
-
self.query_cache_enabled = old
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def clear_query_cache
|
|
47
|
-
Thread.current[:query_cache].try(:clear)
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def select_all(arel, name = nil, binds = [], preparable: nil)
|
|
51
|
-
if self.query_cache_enabled && !locked?(arel)
|
|
52
|
-
arel, binds = binds_from_relation(arel, binds)
|
|
53
|
-
sql = to_sql(arel, binds)
|
|
54
|
-
if ::Rails.version >= '5'
|
|
55
|
-
cache_sql(sql, binds) { super(sql, name, binds, preparable: preparable) }
|
|
56
|
-
else
|
|
57
|
-
cache_sql(sql, binds) { super(sql, name, binds) }
|
|
58
|
-
end
|
|
59
|
-
else
|
|
60
|
-
if ::Rails.version >= '5'
|
|
61
|
-
super
|
|
62
|
-
else
|
|
63
|
-
super(arel, name, binds)
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
# no reason to define these on the including class directly. the super
|
|
69
|
-
# works just as well from a method on the included module
|
|
70
|
-
[:insert, :update, :delete].each do |method_name|
|
|
71
|
-
class_eval <<-end_code, __FILE__, __LINE__ + 1
|
|
72
|
-
def #{method_name}(*args)
|
|
73
|
-
clear_query_cache if self.query_cache_enabled
|
|
74
|
-
super
|
|
75
|
-
end
|
|
76
|
-
end_code
|
|
77
|
-
end
|
|
78
6
|
|
|
79
7
|
private
|
|
80
8
|
|
|
81
|
-
def cache_sql(sql, binds)
|
|
9
|
+
def cache_sql(sql, name, binds)
|
|
82
10
|
# have to include the shard id in the cache key because of switching dbs on the same connection
|
|
83
11
|
sql = "#{self.shard.id}::#{sql}"
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
+
}
|
|
22
|
+
args[:type_casted_binds] = -> { type_casted_binds(binds) } if ::Rails.version >= '5.1.5'
|
|
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
|
|
94
31
|
result.dup
|
|
95
|
-
else
|
|
96
|
-
result.collect { |row| row.dup }
|
|
97
32
|
end
|
|
98
33
|
end
|
|
99
34
|
end
|