switchman 3.0.0 → 3.0.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.
@@ -5,18 +5,6 @@ require 'switchman/errors'
5
5
  module Switchman
6
6
  module ActiveRecord
7
7
  module ConnectionPool
8
- def shard
9
- shard_stack.last || Shard.default
10
- end
11
-
12
- def shard_stack
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)
16
- end
17
- shard_stack
18
- end
19
-
20
8
  def default_schema
21
9
  connection unless @schemas
22
10
  # default shard will not switch databases immediately, so it won't be set yet
@@ -26,15 +14,15 @@ module Switchman
26
14
 
27
15
  def checkout_new_connection
28
16
  conn = super
29
- conn.shard = shard
17
+ conn.shard = current_shard
30
18
  conn
31
19
  end
32
20
 
33
21
  def connection(switch_shard: true)
34
22
  conn = super()
35
- raise NonExistentShardError if shard.new_record?
23
+ raise NonExistentShardError if current_shard.new_record?
36
24
 
37
- switch_database(conn) if conn.shard != shard && switch_shard
25
+ switch_database(conn) if conn.shard != current_shard && switch_shard
38
26
  conn
39
27
  end
40
28
 
@@ -44,26 +32,18 @@ module Switchman
44
32
  flush
45
33
  end
46
34
 
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
35
  def switch_database(conn)
60
- @schemas = conn.current_schemas if !@schemas && conn.adapter_name == 'PostgreSQL' && !shard.database_server.config[:shard_name]
36
+ @schemas = conn.current_schemas if !@schemas && conn.adapter_name == 'PostgreSQL' && !current_shard.database_server.config[:shard_name]
61
37
 
62
- conn.shard = shard
38
+ conn.shard = current_shard
63
39
  end
64
40
 
65
41
  private
66
42
 
43
+ def current_shard
44
+ connection_klass.current_switchman_shard
45
+ end
46
+
67
47
  def tls_key
68
48
  "#{object_id}_shard".to_sym
69
49
  end
@@ -14,15 +14,30 @@ module Switchman
14
14
 
15
15
  def connection
16
16
  conn = super
17
- ::ActiveRecord::Base.connection_pool.switch_database(conn) if conn.shard != ::ActiveRecord::Base.connection_pool.shard
17
+ ::ActiveRecord::Base.connection_pool.switch_database(conn) if conn.shard != ::ActiveRecord::Base.current_switchman_shard
18
18
  conn
19
19
  end
20
20
  end
21
21
 
22
22
  module Migrator
23
+ # significant change: just return MIGRATOR_SALT directly
24
+ # especially if you're going through pgbouncer, the database
25
+ # name you're accessing may not be consistent. it is NOT allowed
26
+ # to run migrations against multiple shards in the same database
27
+ # concurrently
23
28
  def generate_migrator_advisory_lock_id
24
- shard_name_hash = Zlib.crc32("#{Shard.current.id}:#{Shard.current.name}")
25
- ::ActiveRecord::Migrator::MIGRATOR_SALT * shard_name_hash
29
+ ::ActiveRecord::Migrator::MIGRATOR_SALT
30
+ end
31
+
32
+ # significant change: strip out prefer_secondary from config
33
+ def with_advisory_lock_connection
34
+ pool = ::ActiveRecord::ConnectionAdapters::ConnectionHandler.new.establish_connection(
35
+ ::ActiveRecord::Base.connection_db_config.configuration_hash.except(:prefer_secondary)
36
+ )
37
+
38
+ pool.with_connection { |connection| yield(connection) } # rubocop:disable Style/ExplicitBlockArgument
39
+ ensure
40
+ pool&.disconnect!
26
41
  end
27
42
  end
28
43
 
@@ -11,6 +11,13 @@ module Switchman
11
11
  def update_columns(*)
12
12
  shard.activate(self.class.connection_classes) { super }
13
13
  end
14
+
15
+ def delete
16
+ db = shard.database_server
17
+ return db.unguard { super } unless ::GuardRail.environment == db.guard_rail_environment
18
+
19
+ super
20
+ end
14
21
  end
15
22
  end
16
23
  end
@@ -32,7 +32,7 @@ module Switchman
32
32
  end
33
33
 
34
34
  # copy/paste; use quote_local_table_name
35
- def drop_database(name) #:nodoc:
35
+ def drop_database(name) # :nodoc:
36
36
  execute "DROP DATABASE IF EXISTS #{quote_local_table_name(name)}"
37
37
  end
38
38
 
@@ -40,89 +40,36 @@ module Switchman
40
40
  select_values('SELECT * FROM unnest(current_schemas(false))')
41
41
  end
42
42
 
43
- def tables(_name = nil)
44
- query(<<-SQL, 'SCHEMA').map { |row| row[0] }
45
- SELECT tablename
46
- FROM pg_tables
47
- WHERE schemaname = '#{shard.name}'
48
- SQL
49
- end
50
-
51
43
  def extract_schema_qualified_name(string)
52
44
  name = ::ActiveRecord::ConnectionAdapters::PostgreSQL::Utils.extract_schema_qualified_name(string.to_s)
53
45
  name.instance_variable_set(:@schema, shard.name) if string && !name.schema
54
46
  [name.schema, name.identifier]
55
47
  end
56
48
 
57
- def view_exists?(name)
58
- name = ::ActiveRecord::ConnectionAdapters::PostgreSQL::Utils.extract_schema_qualified_name(name.to_s)
59
- return false unless name.identifier
60
-
61
- name.instance_variable_set(:@schema, shard.name) unless name.schema
62
-
63
- select_values(<<-SQL, 'SCHEMA').any?
64
- SELECT c.relname
65
- FROM pg_class c
66
- LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
67
- WHERE c.relkind IN ('v','m') -- (v)iew, (m)aterialized view
68
- AND c.relname = '#{name.identifier}'
69
- AND n.nspname = '#{shard.name}'
70
- SQL
71
- end
72
-
73
- def indexes(table_name)
74
- result = query(<<-SQL, 'SCHEMA')
75
- SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
76
- FROM pg_class t
77
- INNER JOIN pg_index d ON t.oid = d.indrelid
78
- INNER JOIN pg_class i ON d.indexrelid = i.oid
79
- WHERE i.relkind = 'i'
80
- AND d.indisprimary = 'f'
81
- AND t.relname = '#{table_name}'
82
- AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = '#{shard.name}' )
83
- ORDER BY i.relname
84
- SQL
85
-
86
- result.map do |row|
87
- index_name = row[0]
88
- unique = row[1] == true || row[1] == 't'
89
- indkey = row[2].split
90
- inddef = row[3]
91
- oid = row[4]
92
-
93
- columns = Hash[query(<<-SQL, 'SCHEMA')] # rubocop:disable Style/HashConversion
94
- SELECT a.attnum, a.attname
95
- FROM pg_attribute a
96
- WHERE a.attrelid = #{oid}
97
- AND a.attnum IN (#{indkey.join(',')})
98
- SQL
99
-
100
- column_names = columns.stringify_keys.values_at(*indkey).compact
101
-
102
- next if column_names.empty?
103
-
104
- # add info on sort order for columns (only desc order is explicitly specified, asc is the default)
105
- desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
106
- orders = desc_order_columns.any? ? Hash[desc_order_columns.map { |order_column| [order_column, :desc] }] : {} # rubocop:disable Style/HashConversion
107
- where = inddef.scan(/WHERE (.+)$/).flatten[0]
108
- using = inddef.scan(/USING (.+?) /).flatten[0].to_sym
109
-
110
- ::ActiveRecord::ConnectionAdapters::IndexDefinition.new(table_name, index_name, unique, column_names,
111
- orders: orders, where: where, using: using)
112
- end.compact
49
+ # significant change: use the shard name if no explicit schema
50
+ def quoted_scope(name = nil, type: nil)
51
+ schema, name = extract_schema_qualified_name(name)
52
+ type = \
53
+ case type # rubocop:disable Style/HashLikeCase
54
+ when 'BASE TABLE'
55
+ "'r','p'"
56
+ when 'VIEW'
57
+ "'v','m'"
58
+ when 'FOREIGN TABLE'
59
+ "'f'"
60
+ end
61
+ scope = {}
62
+ scope[:schema] = quote(schema || shard.name)
63
+ scope[:name] = quote(name) if name
64
+ scope[:type] = type if type
65
+ scope
113
66
  end
114
67
 
115
- def index_name_exists?(table_name, index_name, _default = nil)
116
- exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i.positive?
117
- SELECT COUNT(*)
118
- FROM pg_class t
119
- INNER JOIN pg_index d ON t.oid = d.indrelid
120
- INNER JOIN pg_class i ON d.indexrelid = i.oid
121
- WHERE i.relkind = 'i'
122
- AND i.relname = '#{index_name}'
123
- AND t.relname = '#{table_name}'
124
- AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = '#{shard.name}' )
125
- SQL
68
+ def foreign_keys(table_name)
69
+ super.each do |fk|
70
+ to_table_qualified_name = ::ActiveRecord::ConnectionAdapters::PostgreSQL::Utils.extract_schema_qualified_name(fk.to_table)
71
+ fk.to_table = to_table_qualified_name.identifier if to_table_qualified_name.schema == shard.name
72
+ end
126
73
  end
127
74
 
128
75
  def quote_local_table_name(name)
@@ -140,6 +87,10 @@ module Switchman
140
87
  name.quoted
141
88
  end
142
89
 
90
+ def with_global_table_name(&block)
91
+ with_local_table_name(false, &block)
92
+ end
93
+
143
94
  def with_local_table_name(enable = true) # rubocop:disable Style/OptionalBooleanParameter
144
95
  old_value = @use_local_table_name
145
96
  @use_local_table_name = enable
@@ -148,41 +99,6 @@ module Switchman
148
99
  @use_local_table_name = old_value
149
100
  end
150
101
 
151
- def foreign_keys(table_name)
152
- # mostly copy-pasted from AR - only change is to the nspname condition for qualified names support
153
- fk_info = select_all <<-SQL.strip_heredoc
154
- 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
155
- FROM pg_constraint c
156
- JOIN pg_class t1 ON c.conrelid = t1.oid
157
- JOIN pg_class t2 ON c.confrelid = t2.oid
158
- JOIN pg_attribute a1 ON a1.attnum = c.conkey[1] AND a1.attrelid = t1.oid
159
- JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid
160
- JOIN pg_namespace t3 ON c.connamespace = t3.oid
161
- WHERE c.contype = 'f'
162
- AND t1.relname = #{quote(table_name)}
163
- AND t3.nspname = '#{shard.name}'
164
- ORDER BY c.conname
165
- SQL
166
-
167
- fk_info.map do |row|
168
- options = {
169
- column: row['column'],
170
- name: row['name'],
171
- primary_key: row['primary_key']
172
- }
173
-
174
- options[:on_delete] = extract_foreign_key_action(row['on_delete'])
175
- options[:on_update] = extract_foreign_key_action(row['on_update'])
176
-
177
- # strip the schema name from to_table if it matches
178
- to_table = row['to_table']
179
- to_table_qualified_name = ::ActiveRecord::ConnectionAdapters::PostgreSQL::Utils.extract_schema_qualified_name(to_table)
180
- to_table = to_table_qualified_name.identifier if to_table_qualified_name.schema == shard.name
181
-
182
- ::ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(table_name, to_table, options)
183
- end
184
- end
185
-
186
102
  def add_index_options(_table_name, _column_name, **)
187
103
  index, algorithm, if_not_exists = super
188
104
  algorithm = nil if DatabaseServer.creating_new_shard && algorithm == 'CONCURRENTLY'
@@ -15,7 +15,7 @@ module Switchman
15
15
  def convert_to_id(value)
16
16
  case value
17
17
  when ::ActiveRecord::Base
18
- value.send(primary_key)
18
+ value.id
19
19
  else
20
20
  super
21
21
  end
@@ -239,6 +239,10 @@ module Switchman
239
239
  connection.with_local_table_name { super }
240
240
  end
241
241
 
242
+ def table_name_matches?(from)
243
+ connection.with_global_table_name { super }
244
+ end
245
+
242
246
  def transpose_predicates(predicates,
243
247
  source_shard,
244
248
  target_shard,
@@ -281,7 +285,7 @@ module Switchman
281
285
  remove = true if type == :primary &&
282
286
  remove_nonlocal_primary_keys &&
283
287
  predicate.left.relation.klass == klass &&
284
- predicate.is_a?(::Arel::Nodes::Equality)
288
+ (predicate.is_a?(::Arel::Nodes::Equality) || predicate.is_a?(::Arel::Nodes::HomogeneousIn))
285
289
 
286
290
  current_source_shard =
287
291
  if source_shard
@@ -301,7 +305,7 @@ module Switchman
301
305
  new_right_value =
302
306
  case right
303
307
  when Array
304
- right.map { |val| transpose_predicate_value(val, current_source_shard, target_shard, type, remove) }
308
+ right.map { |val| transpose_predicate_value(val, current_source_shard, target_shard, type, remove).presence }.compact
305
309
  else
306
310
  transpose_predicate_value(right, current_source_shard, target_shard, type, remove)
307
311
  end
@@ -315,7 +319,13 @@ module Switchman
315
319
  predicate.class.new(predicate.left, right.class.new(new_right_value, right.attribute))
316
320
  end
317
321
  elsif predicate.is_a?(::Arel::Nodes::HomogeneousIn)
318
- predicate.class.new(new_right_value, predicate.attribute, predicate.type)
322
+ # switch to a regular In, so that Relation::WhereClause#contradiction? knows about it
323
+ if new_right_value.empty?
324
+ klass = predicate.type == :in ? ::Arel::Nodes::In : ::Arel::Nodes::NotIn
325
+ klass.new(predicate.attribute, new_right_value)
326
+ else
327
+ predicate.class.new(new_right_value, predicate.attribute, predicate.type)
328
+ end
319
329
  else
320
330
  predicate.class.new(predicate.left, new_right_value)
321
331
  end
@@ -63,7 +63,7 @@ module Switchman
63
63
  %I[update_all delete_all].each do |method|
64
64
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
65
65
  def #{method}(*args)
66
- result = self.activate { |relation| relation.call_super(#{method.inspect}, Relation, *args) }
66
+ result = self.activate(unordered: true) { |relation| relation.call_super(#{method.inspect}, Relation, *args) }
67
67
  result = result.sum if result.is_a?(Array)
68
68
  result
69
69
  end
@@ -94,7 +94,7 @@ module Switchman
94
94
 
95
95
  while ids.first.present?
96
96
  ids.map!(&:to_i) if is_integer
97
- ids << ids.first + batch_size if loose_mode
97
+ ids << (ids.first + batch_size) if loose_mode
98
98
 
99
99
  yield(*ids)
100
100
  last_value = ids.last
@@ -103,7 +103,7 @@ module Switchman
103
103
  end
104
104
  end
105
105
 
106
- def activate(&block)
106
+ def activate(unordered: false, &block)
107
107
  shards = all_shards
108
108
  if Array === shards && shards.length == 1
109
109
  if shards.first == DefaultShard || shards.first == Shard.current(klass.connection_classes)
@@ -112,10 +112,61 @@ module Switchman
112
112
  shards.first.activate(klass.connection_classes) { yield(self, shards.first) }
113
113
  end
114
114
  else
115
- # TODO: implement local limit to avoid querying extra shards
116
- Shard.with_each_shard(shards, [klass.connection_classes]) do
117
- shard(Shard.current(klass.connection_classes), :to_a).activate(&block)
115
+ result_count = 0
116
+ can_order = false
117
+ result = Shard.with_each_shard(shards, [klass.connection_classes]) do
118
+ # don't even query other shards if we're already past the limit
119
+ next if limit_value && result_count >= limit_value && order_values.empty?
120
+
121
+ relation = shard(Shard.current(klass.connection_classes), :to_a)
122
+ # do a minimal query if possible
123
+ relation = relation.limit(limit_value - result_count) if limit_value && !result_count.zero? && order_values.empty?
124
+
125
+ shard_results = relation.activate(&block)
126
+
127
+ if shard_results.present? && !unordered
128
+ can_order ||= can_order_cross_shard_results? unless order_values.empty?
129
+ raise OrderOnMultiShardQuery if !can_order && !order_values.empty? && result_count.positive?
130
+
131
+ result_count += shard_results.is_a?(Array) ? shard_results.length : 1
132
+ end
133
+ shard_results
134
+ end
135
+
136
+ result = reorder_cross_shard_results(result) if can_order
137
+ result.slice!(limit_value..-1) if limit_value
138
+ result
139
+ end
140
+ end
141
+
142
+ def can_order_cross_shard_results?
143
+ # we only presume to be able to post-sort the most basic of orderings
144
+ order_values.all? { |ov| ov.is_a?(::Arel::Nodes::Ordering) && ov.expr.is_a?(::Arel::Attributes::Attribute) }
145
+ end
146
+
147
+ def reorder_cross_shard_results(results)
148
+ results.sort! do |l, r|
149
+ result = 0
150
+ order_values.each do |ov|
151
+ if l.is_a?(::ActiveRecord::Base)
152
+ a = l.attribute(ov.expr.name)
153
+ b = r.attribute(ov.expr.name)
154
+ else
155
+ a, b = l, r
156
+ end
157
+ next if a == b
158
+
159
+ if a.nil? || b.nil?
160
+ result = 1 if a.nil?
161
+ result *= -1 if ov.is_a?(::Arel::Nodes::Descending)
162
+ else
163
+ result = a <=> b
164
+ end
165
+
166
+ result *= -1 if ov.is_a?(::Arel::Nodes::Descending)
167
+ break unless result.zero?
118
168
  end
169
+ result
119
170
  end
120
171
  end
121
172
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Switchman
4
+ module ActiveRecord
5
+ module TestFixtures
6
+ FORBIDDEN_DB_ENVS = %i[development production].freeze
7
+ def setup_fixtures(config = ::ActiveRecord::Base)
8
+ super
9
+
10
+ return unless run_in_transaction?
11
+
12
+ # Replace the one that activerecord natively uses with a switchman-optimized one
13
+ ::ActiveSupport::Notifications.unsubscribe(@connection_subscriber)
14
+ # Code adapted from the code in rails proper
15
+ @connection_subscriber = ::ActiveSupport::Notifications.subscribe('!connection.active_record') do |_, _, _, _, payload|
16
+ spec_name = payload[:spec_name] if payload.key?(:spec_name)
17
+ shard = payload[:shard] if payload.key?(:shard)
18
+ setup_shared_connection_pool
19
+
20
+ if spec_name && !FORBIDDEN_DB_ENVS.include?(shard)
21
+ begin
22
+ connection = ::ActiveRecord::Base.connection_handler.retrieve_connection(spec_name, shard: shard)
23
+ rescue ::ActiveRecord::ConnectionNotEstablished, ::ActiveRecord::NoDatabaseError
24
+ connection = nil
25
+ end
26
+
27
+ if connection && !@fixture_connections.include?(connection)
28
+ connection.begin_transaction joinable: false, _lazy: false
29
+ connection.pool.lock_thread = true if lock_threads
30
+ @fixture_connections << connection
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ def enlist_fixture_connections
37
+ setup_shared_connection_pool
38
+
39
+ ::ActiveRecord::Base.connection_handler.connection_pool_list.reject { |cp| FORBIDDEN_DB_ENVS.include?(cp.db_config.env_name.to_sym) }.map(&:connection)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -59,7 +59,7 @@ module Switchman
59
59
  end
60
60
 
61
61
  def database_servers
62
- unless @database_servers
62
+ if !@database_servers || @database_servers.empty?
63
63
  @database_servers = {}.with_indifferent_access
64
64
  ::ActiveRecord::Base.configurations.configurations.each do |config|
65
65
  if config.name.include?('/')
@@ -89,13 +89,13 @@ module Switchman
89
89
  end
90
90
 
91
91
  def connects_to_hash
92
- self.class.all_roles.map do |role|
92
+ self.class.all_roles.to_h do |role|
93
93
  config_role = role
94
94
  config_role = :primary unless roles.include?(role)
95
95
  config_name = :"#{id}/#{config_role}"
96
96
  config_name = :primary if id == ::Rails.env && config_role == :primary
97
97
  [role.to_sym, config_name]
98
- end.to_h
98
+ end
99
99
  end
100
100
 
101
101
  def destroy
@@ -113,7 +113,7 @@ module Switchman
113
113
  end
114
114
 
115
115
  def config(environment = :primary)
116
- @configs[environment] ||= begin
116
+ @configs[environment] ||=
117
117
  case @config[environment]
118
118
  when Array
119
119
  @config[environment].map do |config|
@@ -127,7 +127,6 @@ module Switchman
127
127
  else
128
128
  @config
129
129
  end
130
- end
131
130
  end
132
131
 
133
132
  def guard_rail_environment
@@ -187,58 +186,56 @@ module Switchman
187
186
 
188
187
  name ||= "#{config[:database]}_shard_#{id}"
189
188
 
189
+ schema_already_existed = false
190
+ shard = nil
190
191
  Shard.connection.transaction do
191
- shard = Shard.create!(id: id,
192
- name: name,
193
- database_server_id: self.id)
194
- schema_already_existed = false
195
-
196
- begin
197
- self.class.creating_new_shard = true
198
- DatabaseServer.send(:reference_role, :deploy)
199
- ::ActiveRecord::Base.connected_to(shard: self.id.to_sym, role: :deploy) do
200
- if create_statement
201
- if ::ActiveRecord::Base.connection.select_value("SELECT 1 FROM pg_namespace WHERE nspname=#{::ActiveRecord::Base.connection.quote(name)}")
202
- schema_already_existed = true
203
- raise 'This schema already exists; cannot overwrite'
204
- end
205
- Array(create_statement.call).each do |stmt|
206
- ::ActiveRecord::Base.connection.execute(stmt)
207
- end
192
+ self.class.creating_new_shard = true
193
+ DatabaseServer.send(:reference_role, :deploy)
194
+ ::ActiveRecord::Base.connected_to(shard: self.id.to_sym, role: :deploy) do
195
+ shard = Shard.create!(id: id,
196
+ name: name,
197
+ database_server_id: self.id)
198
+ if create_statement
199
+ if ::ActiveRecord::Base.connection.select_value("SELECT 1 FROM pg_namespace WHERE nspname=#{::ActiveRecord::Base.connection.quote(name)}")
200
+ schema_already_existed = true
201
+ raise 'This schema already exists; cannot overwrite'
208
202
  end
209
- if config[:adapter] == 'postgresql'
210
- old_proc = ::ActiveRecord::Base.connection.raw_connection.set_notice_processor do
211
- end
203
+ Array(create_statement.call).each do |stmt|
204
+ ::ActiveRecord::Base.connection.execute(stmt)
212
205
  end
213
- old_verbose = ::ActiveRecord::Migration.verbose
214
- ::ActiveRecord::Migration.verbose = false
215
-
216
- unless schema == false
217
- shard.activate do
218
- reset_column_information
219
-
220
- ::ActiveRecord::Base.connection.transaction(requires_new: true) do
221
- ::ActiveRecord::Base.connection.migration_context.migrate
222
- end
223
- reset_column_information
224
- ::ActiveRecord::Base.descendants.reject do |m|
225
- m <= UnshardedRecord || !m.table_exists?
226
- end.each(&:define_attribute_methods)
206
+ end
207
+ if config[:adapter] == 'postgresql'
208
+ old_proc = ::ActiveRecord::Base.connection.raw_connection.set_notice_processor do
209
+ end
210
+ end
211
+ old_verbose = ::ActiveRecord::Migration.verbose
212
+ ::ActiveRecord::Migration.verbose = false
213
+
214
+ unless schema == false
215
+ shard.activate do
216
+ reset_column_information
217
+
218
+ ::ActiveRecord::Base.connection.transaction(requires_new: true) do
219
+ ::ActiveRecord::Base.connection.migration_context.migrate
227
220
  end
221
+ reset_column_information
222
+ ::ActiveRecord::Base.descendants.reject do |m|
223
+ m <= UnshardedRecord || !m.table_exists?
224
+ end.each(&:define_attribute_methods)
228
225
  end
229
- ensure
230
- ::ActiveRecord::Migration.verbose = old_verbose
231
- ::ActiveRecord::Base.connection.raw_connection.set_notice_processor(&old_proc) if old_proc
232
226
  end
233
- shard
234
- rescue
235
- shard.destroy
236
- shard.drop_database rescue nil unless schema_already_existed
237
- reset_column_information unless schema == false rescue nil
238
- raise
239
227
  ensure
240
- self.class.creating_new_shard = false
228
+ ::ActiveRecord::Migration.verbose = old_verbose
229
+ ::ActiveRecord::Base.connection.raw_connection.set_notice_processor(&old_proc) if old_proc
241
230
  end
231
+ shard
232
+ rescue
233
+ shard&.destroy
234
+ shard&.drop_database rescue nil unless schema_already_existed
235
+ reset_column_information unless schema == false rescue nil
236
+ raise
237
+ ensure
238
+ self.class.creating_new_shard = false
242
239
  end
243
240
  end
244
241
 
@@ -90,6 +90,7 @@ module Switchman
90
90
  require 'switchman/active_record/statement_cache'
91
91
  require 'switchman/active_record/tasks/database_tasks'
92
92
  require 'switchman/active_record/type_caster'
93
+ require 'switchman/active_record/test_fixtures'
93
94
  require 'switchman/arel'
94
95
  require 'switchman/call_super'
95
96
  require 'switchman/rails'
@@ -101,7 +102,7 @@ module Switchman
101
102
  self.default_shard = ::Rails.env.to_sym
102
103
  self.default_role = :primary
103
104
 
104
- include ActiveRecord::Base
105
+ prepend ActiveRecord::Base
105
106
  include ActiveRecord::AttributeMethods
106
107
  include ActiveRecord::Persistence
107
108
  singleton_class.prepend ActiveRecord::ModelSchema::ClassMethods
@@ -150,9 +151,12 @@ module Switchman
150
151
  ::ActiveRecord::Relation.include(CallSuper)
151
152
 
152
153
  ::ActiveRecord::PredicateBuilder::AssociationQueryValue.prepend(ActiveRecord::PredicateBuilder::AssociationQueryValue)
154
+ ::ActiveRecord::PredicateBuilder::PolymorphicArrayValue.prepend(ActiveRecord::PredicateBuilder::AssociationQueryValue)
153
155
 
154
156
  ::ActiveRecord::Tasks::DatabaseTasks.singleton_class.prepend(ActiveRecord::Tasks::DatabaseTasks)
155
157
 
158
+ ::ActiveRecord::TestFixtures.prepend(ActiveRecord::TestFixtures)
159
+
156
160
  ::ActiveRecord::TypeCaster::Map.include(ActiveRecord::TypeCaster::Map)
157
161
  ::ActiveRecord::TypeCaster::Connection.include(ActiveRecord::TypeCaster::Connection)
158
162