switchman 1.14.0 → 1.15.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.
@@ -30,17 +30,14 @@ module Switchman
30
30
  end
31
31
 
32
32
  module CollectionAssociation
33
- method = ::Rails.version < '5.1' ? :get_records : :find_target
34
- module_eval <<-RUBY, __FILE__, __LINE__ + 1
35
- def #{method}
36
- shards = reflection.options[:multishard] && owner.respond_to?(:associated_shards) ? owner.associated_shards : [shard]
37
- # activate both the owner and the target's shard category, so that Reflection#join_id_for,
38
- # when called for the owner, will be returned relative to shard the query will execute on
39
- Shard.with_each_shard(shards, [klass.shard_category, owner.class.shard_category].uniq) do
40
- super
41
- end
33
+ def find_target
34
+ shards = reflection.options[:multishard] && owner.respond_to?(:associated_shards) ? owner.associated_shards : [shard]
35
+ # activate both the owner and the target's shard category, so that Reflection#join_id_for,
36
+ # when called for the owner, will be returned relative to shard the query will execute on
37
+ Shard.with_each_shard(shards, [klass.shard_category, owner.class.shard_category].uniq) do
38
+ super
42
39
  end
43
- RUBY
40
+ end
44
41
  end
45
42
 
46
43
  module BelongsToAssociation
@@ -88,7 +85,11 @@ module Switchman
88
85
  # Copypasta from Activerecord but with added global_id_for goodness.
89
86
  def records_for(ids)
90
87
  scope.where(association_key_name => ids).load do |record|
91
- global_key = Shard.global_id_for(record[association_key_name], record.shard)
88
+ global_key = if record.class.shard_category == :unsharded
89
+ convert_key(record[association_key_name])
90
+ else
91
+ Shard.global_id_for(record[association_key_name], record.shard)
92
+ end
92
93
  owner = owners_by_key[global_key.to_s].first
93
94
  association = owner.association(reflection.name)
94
95
  association.set_inverse_instance(record)
@@ -198,6 +199,13 @@ module Switchman
198
199
  (record.has_attribute?(reflection.foreign_key) && record.send(reflection.foreign_key) != key) || # have to use send instead of [] because sharding
199
200
  record.attribute_changed?(reflection.foreign_key)
200
201
  end
202
+
203
+ def save_belongs_to_association(reflection)
204
+ # this seems counter-intuitive, but the autosave code will assign to attribute bypassing switchman,
205
+ # after reading the id attribute _without_ bypassing switchman. So we need Shard.current for the
206
+ # category of the associated record to match Shard.current for the category of self
207
+ shard.activate(shard_category_for_reflection(reflection)) { super }
208
+ end
201
209
  end
202
210
  end
203
211
  end
@@ -61,6 +61,9 @@ module Switchman
61
61
  end
62
62
  end
63
63
 
64
+ # see also Base#shard_category_for_reflection
65
+ # the difference being this will output static strings for the common cases, making them
66
+ # more performant
64
67
  def shard_category_code_for_reflection(reflection)
65
68
  if reflection
66
69
  if reflection.options[:polymorphic]
@@ -68,6 +68,14 @@ module Switchman
68
68
  result
69
69
  end
70
70
  end
71
+
72
+ def clear_query_caches_for_current_thread
73
+ ::ActiveRecord::Base.connection_handlers.each_value do |handler|
74
+ handler.connection_pool_list.each do |pool|
75
+ pool.connection(switch_shard: false).clear_query_cache if pool.active_connection?
76
+ end
77
+ end
78
+ end
71
79
  end
72
80
 
73
81
  def self.included(klass)
@@ -99,12 +107,12 @@ module Switchman
99
107
 
100
108
  def save(*args)
101
109
  @shard_set_in_stone = true
102
- self.class.shard(shard, :implicit).scoping { super }
110
+ (self.class.current_scope || self.class.default_scoped).shard(shard, :implicit).scoping { super }
103
111
  end
104
112
 
105
113
  def save!(*args)
106
114
  @shard_set_in_stone = true
107
- self.class.shard(shard, :implicit).scoping { super }
115
+ (self.class.current_scope || self.class.default_scoped).shard(shard, :implicit).scoping { super }
108
116
  end
109
117
 
110
118
  def destroy
@@ -146,6 +154,27 @@ module Switchman
146
154
  # do this the Rails 4.2 way, so that if Shard.current != self.shard, the id gets transposed
147
155
  self.class.connection.quote(id)
148
156
  end
157
+
158
+ protected
159
+
160
+ # see also AttributeMethods#shard_category_code_for_reflection
161
+ def shard_category_for_reflection(reflection)
162
+ if reflection
163
+ if reflection.options[:polymorphic]
164
+ begin
165
+ read_attribute(reflection.foreign_type)&.constantize&.shard_category || :primary
166
+ rescue NameError
167
+ # in case someone is abusing foreign_type to not point to an actual class
168
+ :primary
169
+ end
170
+ else
171
+ # otherwise we can just return a symbol for the statically known type of the association
172
+ reflection.klass.shard_category
173
+ end
174
+ else
175
+ shard_category
176
+ end
177
+ end
149
178
  end
150
179
  end
151
180
  end
@@ -121,9 +121,8 @@ module Switchman
121
121
  group_fields = group_attrs
122
122
  end
123
123
 
124
- # .clone below corrects for what I consider a Rails bug. column_alias_for modifies the string in place.
125
124
  # to_s is because Rails 5 returns a string but Rails 6 returns a symbol.
126
- group_aliases = group_fields.map { |field| column_alias_for(field.clone).to_s }
125
+ group_aliases = group_fields.map { |field| column_alias_for(field.downcase.to_s).to_s }
127
126
  group_columns = group_aliases.zip(group_fields).map { |aliaz, field|
128
127
  [aliaz, type_for(field), column_name_for(field)]
129
128
  }
@@ -24,6 +24,10 @@ module Switchman
24
24
  end
25
25
 
26
26
  def establish_connection(spec)
27
+ # Just skip establishing a sharded connection if sharding isn't loaded; we'll do it again later
28
+ # This only can happen when loading ActiveRecord::Base; after everything is loaded Shard will
29
+ # be defined and this will actually establish a connection
30
+ return unless defined?(Shard)
27
31
  pool = super
28
32
 
29
33
  # this is the first place that the adapter would have been required; but now we
@@ -39,16 +43,24 @@ module Switchman
39
43
  # to sharding will recurse onto itself trying to access column information
40
44
  Shard.default
41
45
 
46
+ config = pool.spec.config
42
47
  # automatically change config to allow for sharing connections with simple config
43
- config = ::Rails.version < '5.1' ? spec.config : pool.spec.config
44
48
  ConnectionHandler.make_sharing_automagic(config)
45
49
  ConnectionHandler.make_sharing_automagic(Shard.default.database_server.config)
46
50
 
47
- if ::Rails.version < '5.1'
48
- ::ActiveRecord::Base.configurations[::Rails.env] = spec.instance_variable_get(:@config).stringify_keys
49
- else
51
+ if ::Rails.version < '6.0'
50
52
  ::ActiveRecord::Base.configurations[::Rails.env] = config.stringify_keys
53
+ else
54
+ # Adopted from the deprecated code that currently lives in rails proper
55
+ remaining_configs = ::ActiveRecord::Base.configurations.configurations.reject { |db_config| db_config.env_name == ::Rails.env }
56
+ new_config = ::ActiveRecord::DatabaseConfigurations.new(::Rails.env => config.stringify_keys).configurations
57
+ new_configs = remaining_configs + new_config
58
+
59
+ ::ActiveRecord::Base.configurations = new_configs
51
60
  end
61
+ else
62
+ # this is probably wrong now
63
+ Shard.default.remove_instance_variable(:@name) if Shard.default.instance_variable_defined?(:@name)
52
64
  end
53
65
 
54
66
  @shard_connection_pools ||= { [:master, Shard.default.database_server.shareable? ? ::Rails.env : Shard.default] => pool}
@@ -122,8 +134,7 @@ module Switchman
122
134
  else
123
135
  ancestor_pool.spec
124
136
  end
125
- spec = spec.to_hash if ::Rails.version >= '5.1'
126
- pool = establish_connection spec
137
+ pool = establish_connection(spec.to_hash)
127
138
  pool.instance_variable_set(:@schema_cache, ancestor_pool.schema_cache) if ancestor_pool.schema_cache
128
139
  pool
129
140
  elsif spec_name != "primary"
@@ -32,10 +32,10 @@ module Switchman
32
32
  conn
33
33
  end
34
34
 
35
- def connection
36
- conn = super
35
+ def connection(switch_shard: true)
36
+ conn = super()
37
37
  raise NonExistentShardError if shard.new_record?
38
- switch_database(conn) if conn.shard != self.shard
38
+ switch_database(conn) if conn.shard != self.shard && switch_shard
39
39
  conn
40
40
  end
41
41
 
@@ -47,6 +47,20 @@ module Switchman
47
47
  end
48
48
  end
49
49
 
50
+ def remove_shard!(shard)
51
+ synchronize do
52
+ # The shard might be currently active, so we need to update our own shard
53
+ if self.shard == shard
54
+ self.shard = Shard.default
55
+ end
56
+ # Update out any connections that may be using this shard
57
+ @connections.each do |conn|
58
+ # This will also update the connection's shard to the default shard
59
+ switch_database(conn) if conn.shard == shard
60
+ end
61
+ end
62
+ end
63
+
50
64
  def clear_idle_connections!(since_when)
51
65
  synchronize do
52
66
  @connections.reject! do |conn|
@@ -18,18 +18,14 @@ module Switchman
18
18
  shard = " [#{shard[:database_server_id]}:#{shard[:id]} #{shard[:env]}]" if shard
19
19
 
20
20
  unless (payload[:binds] || []).empty?
21
- if ::Rails.version < '5.0.3'
22
- binds = " " + payload[:binds].map { |attr| render_bind(attr) }.inspect
23
- else
24
- use_old_format = (::Rails.version < '5.1') ? (::Rails.version < '5.0.7') : (::Rails.version < '5.1.5')
25
- args = use_old_format ?
26
- [payload[:binds], payload[:type_casted_binds]] :
27
- [payload[:type_casted_binds]]
28
- casted_params = type_casted_binds(*args)
29
- binds = " " + payload[:binds].zip(casted_params).map { |attr, value|
30
- render_bind(attr, value)
31
- }.inspect
32
- end
21
+ use_old_format = (::Rails.version < '5.1.5')
22
+ args = use_old_format ?
23
+ [payload[:binds], payload[:type_casted_binds]] :
24
+ [payload[:type_casted_binds]]
25
+ casted_params = type_casted_binds(*args)
26
+ binds = " " + payload[:binds].zip(casted_params).map { |attr, value|
27
+ render_bind(attr, value)
28
+ }.inspect
33
29
  end
34
30
 
35
31
  name = colorize_payload_name(name, payload[:name])
@@ -4,7 +4,7 @@ module Switchman
4
4
  module ClassMethods
5
5
  def quoted_table_name
6
6
  @quoted_table_name ||= {}
7
- @quoted_table_name[Shard.current.id] ||= connection.quote_table_name(table_name)
7
+ @quoted_table_name[Shard.current(shard_category).id] ||= connection.quote_table_name(table_name)
8
8
  end
9
9
  end
10
10
  end
@@ -52,31 +52,12 @@ module Switchman
52
52
  SQL
53
53
  end
54
54
 
55
- if ::Rails.version >= '5.1'
56
- def extract_schema_qualified_name(string)
57
- name = ::ActiveRecord::ConnectionAdapters::PostgreSQL::Utils.extract_schema_qualified_name(string.to_s)
58
- if string && !name.schema && use_qualified_names?
59
- name.instance_variable_set(:@schema, shard.name)
60
- end
61
- [name.schema, name.identifier]
62
- end
63
- else
64
- def data_source_exists?(name)
65
- name = ::ActiveRecord::ConnectionAdapters::PostgreSQL::Utils.extract_schema_qualified_name(name.to_s)
66
- return false unless name.identifier
67
- if !name.schema && use_qualified_names?
68
- name.instance_variable_set(:@schema, shard.name)
69
- end
70
-
71
- exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
72
- SELECT COUNT(*)
73
- FROM pg_class c
74
- LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
75
- WHERE c.relkind IN ('r','v','m') -- (r)elation/table, (v)iew, (m)aterialized view
76
- AND c.relname = '#{name.identifier}'
77
- AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'}
78
- SQL
55
+ def extract_schema_qualified_name(string)
56
+ name = ::ActiveRecord::ConnectionAdapters::PostgreSQL::Utils.extract_schema_qualified_name(string.to_s)
57
+ if string && !name.schema && use_qualified_names?
58
+ name.instance_variable_set(:@schema, shard.name)
79
59
  end
60
+ [name.schema, name.identifier]
80
61
  end
81
62
 
82
63
  def view_exists?(name)
@@ -175,11 +156,12 @@ module Switchman
175
156
  name.quoted
176
157
  end
177
158
 
178
- def with_local_table_name
179
- @use_local_table_name = true
159
+ def with_local_table_name(enable = true)
160
+ old_value = @use_local_table_name
161
+ @use_local_table_name = enable
180
162
  yield
181
163
  ensure
182
- @use_local_table_name = false
164
+ @use_local_table_name = old_value
183
165
  end
184
166
 
185
167
  def foreign_keys(table_name)
@@ -248,6 +230,10 @@ module Switchman
248
230
 
249
231
  execute "ALTER INDEX #{quote_table_name(old_name)} RENAME TO #{quote_local_table_name(new_name)}"
250
232
  end
233
+
234
+ def columns(*)
235
+ with_local_table_name(false) { super }
236
+ end
251
237
  end
252
238
  end
253
239
  end
@@ -1,122 +1,32 @@
1
1
  module Switchman
2
2
  module ActiveRecord
3
3
  module QueryCache
4
- if ::Rails.version < '5.0.1'
5
- # thread local accessors to replace @query_cache_enabled
6
- def query_cache
7
- thread_cache = Thread.current[:query_cache] ||= {}
8
- thread_cache[self.object_id] ||= Hash.new { |h,sql| h[sql] = {} }
9
- end
10
-
11
- def query_cache_enabled
12
- Thread.current[:query_cache_enabled]
13
- end
14
-
15
- def query_cache_enabled=(value)
16
- Thread.current[:query_cache_enabled] = value
17
- end
18
-
19
- # basically wholesale repeat of the methods from the original (see
20
- # https://github.com/rails/rails/blob/master/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb),
21
- # but with self.query_cache_enabled and self.query_cache_enabled= instead
22
- # of @query_cache_enabled.
23
-
24
- def enable_query_cache!
25
- self.query_cache_enabled = true
26
- end
27
-
28
- def disable_query_cache!
29
- self.query_cache_enabled = false
30
- end
31
-
32
- def cache
33
- old, self.query_cache_enabled = query_cache_enabled, true
34
- yield
35
- ensure
36
- self.query_cache_enabled = old
37
- clear_query_cache unless self.query_cache_enabled
38
- end
39
-
40
- def uncached
41
- old, self.query_cache_enabled = query_cache_enabled, false
42
- yield
43
- ensure
44
- self.query_cache_enabled = old
45
- end
46
-
47
- def clear_query_cache
48
- Thread.current[:query_cache]&.clear
49
- end
50
-
51
- def select_all(arel, name = nil, binds = [], preparable: nil)
52
- if self.query_cache_enabled && !locked?(arel)
53
- arel, binds = binds_from_relation(arel, binds)
54
- sql = to_sql(arel, binds)
55
- cache_sql(sql, binds) { super(sql, name, binds, preparable: preparable) }
56
- else
57
- super
58
- end
59
- end
60
-
61
- # no reason to define these on the including class directly. the super
62
- # works just as well from a method on the included module
63
- [:insert, :update, :delete].each do |method_name|
64
- class_eval <<-end_code, __FILE__, __LINE__ + 1
65
- def #{method_name}(*args)
66
- clear_query_cache if self.query_cache_enabled
67
- super
68
- end
69
- end_code
70
- end
71
- end
72
4
 
73
5
  private
74
6
 
75
- if ::Rails.version < '5.1'
76
- def cache_sql(sql, binds)
77
- # have to include the shard id in the cache key because of switching dbs on the same connection
78
- sql = "#{self.shard.id}::#{sql}"
7
+ def cache_sql(sql, name, binds)
8
+ # have to include the shard id in the cache key because of switching dbs on the same connection
9
+ sql = "#{self.shard.id}::#{sql}"
10
+ @lock.synchronize do
79
11
  result =
80
12
  if query_cache[sql].key?(binds)
81
- args = {:sql => sql, :binds => binds, :name => "CACHE", :connection_id => object_id}
82
- args[:type_casted_binds] = -> { type_casted_binds(binds) } if ::Rails.version >= '5.0.7'
83
- ::ActiveSupport::Notifications.instrument("sql.active_record", args)
13
+ args = {
14
+ sql: sql,
15
+ binds: binds,
16
+ name: name,
17
+ connection_id: object_id,
18
+ cached: true
19
+ }
20
+ args[:type_casted_binds] = -> { type_casted_binds(binds) } if ::Rails.version >= '5.1.5'
21
+ ::ActiveSupport::Notifications.instrument(
22
+ "sql.active_record",
23
+ args
24
+ )
84
25
  query_cache[sql][binds]
85
26
  else
86
27
  query_cache[sql][binds] = yield
87
28
  end
88
-
89
- if ::ActiveRecord::Result === result
90
- result.dup
91
- else
92
- result.collect { |row| row.dup }
93
- end
94
- end
95
- else
96
- def cache_sql(sql, name, binds)
97
- # have to include the shard id in the cache key because of switching dbs on the same connection
98
- sql = "#{self.shard.id}::#{sql}"
99
- @lock.synchronize do
100
- result =
101
- if query_cache[sql].key?(binds)
102
- args = {
103
- sql: sql,
104
- binds: binds,
105
- name: name,
106
- connection_id: object_id,
107
- cached: true
108
- }
109
- args[:type_casted_binds] = -> { type_casted_binds(binds) } if ::Rails.version >= '5.1.5'
110
- ::ActiveSupport::Notifications.instrument(
111
- "sql.active_record",
112
- args
113
- )
114
- query_cache[sql][binds]
115
- else
116
- query_cache[sql][binds] = yield
117
- end
118
- result.dup
119
- end
29
+ result.dup
120
30
  end
121
31
  end
122
32
  end
@@ -56,15 +56,7 @@ module Switchman
56
56
  end
57
57
  end
58
58
 
59
- if ::Rails.version < '5.1'
60
- def generic_query_builder(connection)
61
- @query_builder ||= connection.cacheable_query(@arel)
62
- end
63
-
64
- def qualified_query_builder(shard, klass)
65
- @qualified_query_builders[shard.id] ||= klass.connection.cacheable_query(@arel)
66
- end
67
- elsif ::Rails.version < '5.2'
59
+ if ::Rails.version < '5.2'
68
60
  def generic_query_builder(connection)
69
61
  @query_builder ||= connection.cacheable_query(self.class, @arel)
70
62
  end
@@ -4,10 +4,26 @@ module Switchman
4
4
  module ClassMethods
5
5
  def lookup_store(*store_options)
6
6
  store = super
7
+ # can't use defined?, because it's a _ruby_ autoloaded constant,
8
+ # so just checking that will cause it to get required
9
+ if store.class.name == "ActiveSupport::Cache::RedisCacheStore" && !::ActiveSupport::Cache::RedisCacheStore.ancestors.include?(RedisCacheStore)
10
+ ::ActiveSupport::Cache::RedisCacheStore.prepend(RedisCacheStore)
11
+ end
7
12
  store.options[:namespace] ||= lambda { Shard.current.default? ? nil : "shard_#{Shard.current.id}" }
8
13
  store
9
14
  end
10
15
  end
16
+
17
+ module RedisCacheStore
18
+ def clear(options = {})
19
+ # RedisCacheStore tries to be smart and only clear the cache under your namespace, if you have one set
20
+ # unfortunately, it uses the keys command, which is extraordinarily inefficient in a large redis instance
21
+ # fortunately, we can assume we control the entire instance, because we set up the namespacing, so just
22
+ # always unset it temporarily for clear calls
23
+ options[:namespace] = nil
24
+ super
25
+ end
26
+ end
11
27
  end
12
28
  end
13
29
  end
@@ -23,7 +23,14 @@ module Switchman
23
23
  @category = category
24
24
  @default_pool = default_pool
25
25
  @connection_pools = shard_connection_pools
26
- @schema_cache = SchemaCache.new(self)
26
+ @schema_cache = default_pool.get_schema_cache(nil) if ::Rails.version >= '6'
27
+ @schema_cache = SchemaCache.new(self) unless @schema_cache.is_a?(SchemaCache)
28
+ if ::Rails.version >= '6'
29
+ @default_pool.set_schema_cache(@schema_cache)
30
+ @connection_pools.each_value do |pool|
31
+ pool.set_schema_cache(@schema_cache)
32
+ end
33
+ end
27
34
  end
28
35
 
29
36
  def active_shard
@@ -46,11 +53,11 @@ module Switchman
46
53
  connection_pools.map(&:connections).inject([], &:+)
47
54
  end
48
55
 
49
- def connection
56
+ def connection(switch_shard: true)
50
57
  pool = current_pool
51
58
  begin
52
- connection = pool.connection
53
- connection.instance_variable_set(:@schema_cache, @schema_cache)
59
+ connection = pool.connection(switch_shard: switch_shard)
60
+ connection.instance_variable_set(:@schema_cache, @schema_cache) unless ::Rails.version >= '6'
54
61
  connection
55
62
  rescue ConnectionError
56
63
  raise if active_shard.database_server == Shard.default.database_server && active_shackles_environment == :master
@@ -60,7 +67,7 @@ module Switchman
60
67
  pool = create_pool(config.dup)
61
68
  begin
62
69
  connection = pool.connection
63
- connection.instance_variable_set(:@schema_cache, @schema_cache)
70
+ connection.instance_variable_set(:@schema_cache, @schema_cache) unless ::Rails.version >= '6'
64
71
  rescue ConnectionError
65
72
  raise if idx == configs.length - 1
66
73
  next
@@ -71,6 +78,10 @@ module Switchman
71
78
  end
72
79
  end
73
80
 
81
+ def get_schema_cache(_connection)
82
+ @schema_cache
83
+ end
84
+
74
85
  %w{release_connection
75
86
  disconnect!
76
87
  flush!
@@ -98,6 +109,10 @@ module Switchman
98
109
  connection_pools.each { |pool| pool.clear_idle_connections!(since_when) }
99
110
  end
100
111
 
112
+ def remove_shard!(shard)
113
+ connection_pools.each { |pool| pool.remove_shard!(shard) }
114
+ end
115
+
101
116
  protected
102
117
 
103
118
  def connection_pools
@@ -132,18 +147,19 @@ module Switchman
132
147
  end
133
148
  end
134
149
  end
135
- args = [config, "#{config[:adapter]}_connection"]
136
- args.unshift(pool_key.join("/"))
137
- spec = ::ActiveRecord::ConnectionAdapters::ConnectionSpecification.new(*args)
150
+ spec = ::ActiveRecord::ConnectionAdapters::ConnectionSpecification.new(
151
+ category,
152
+ config,
153
+ "#{config[:adapter]}_connection"
154
+ )
138
155
  # unfortunately the AR code that does this require logic can't really be
139
156
  # called in isolation
140
157
  require "active_record/connection_adapters/#{config[:adapter]}_adapter"
141
158
 
142
159
  ::ActiveRecord::ConnectionAdapters::ConnectionPool.new(spec).tap do |pool|
143
160
  pool.shard = shard
144
- if ::Rails.version >= '5.0.1'
145
- pool.enable_query_cache! if !@connection_pools.empty? && @connection_pools.first.last.query_cache_enabled
146
- end
161
+ pool.set_schema_cache(@schema_cache) if ::Rails.version >= '6'
162
+ pool.enable_query_cache! if !@connection_pools.empty? && @connection_pools.first.last.query_cache_enabled
147
163
  end
148
164
  end
149
165
  end
@@ -185,6 +185,7 @@ module Switchman
185
185
  shard = Shard.create!(:id => shard_id,
186
186
  :name => name,
187
187
  :database_server => self)
188
+ schema_already_existed = false
188
189
 
189
190
  begin
190
191
  self.class.creating_new_shard = true
@@ -192,6 +193,10 @@ module Switchman
192
193
  ::Shackles.activate(:deploy) do
193
194
  begin
194
195
  if create_statement
196
+ if (::ActiveRecord::Base.connection.select_value("SELECT 1 FROM pg_namespace WHERE nspname=#{::ActiveRecord::Base.connection.quote(name)}"))
197
+ schema_already_existed = true
198
+ raise "This schema already exists; cannot overwrite"
199
+ end
195
200
  Array(create_statement.call).each do |stmt|
196
201
  ::ActiveRecord::Base.connection.execute(stmt)
197
202
  end
@@ -229,7 +234,9 @@ module Switchman
229
234
  shard
230
235
  rescue
231
236
  shard.destroy
232
- shard.drop_database rescue nil
237
+ unless schema_already_existed
238
+ shard.drop_database rescue nil
239
+ end
233
240
  reset_column_information unless create_schema == false rescue nil
234
241
  raise
235
242
  ensure
@@ -3,6 +3,7 @@ require 'switchman/database_server'
3
3
  module Switchman
4
4
  class DefaultShard
5
5
  def id; 'default'; end
6
+ alias cache_key id
6
7
  def activate(*categories); yield; end
7
8
  def activate!(*categories); end
8
9
  def default?; true; end
@@ -88,7 +88,7 @@ module Switchman
88
88
  require "switchman/call_super"
89
89
  require "switchman/rails"
90
90
  require "switchman/shackles/relation"
91
- require_dependency "switchman/shard_internal"
91
+ require_dependency "switchman/shard"
92
92
  require "switchman/standard_error"
93
93
 
94
94
  ::StandardError.include(StandardError)
@@ -118,11 +118,6 @@ module Switchman
118
118
  ::ActiveRecord::ConnectionAdapters::ConnectionHandler.prepend(ActiveRecord::ConnectionHandler)
119
119
  ::ActiveRecord::ConnectionAdapters::ConnectionPool.prepend(ActiveRecord::ConnectionPool)
120
120
  ::ActiveRecord::ConnectionAdapters::AbstractAdapter.prepend(ActiveRecord::QueryCache)
121
- # when we call super in Switchman::ActiveRecord::QueryCache#select_all,
122
- # we want it to find the definition from
123
- # ActiveRecord::ConnectionAdapters::DatabaseStatements, not
124
- # ActiveRecord::ConnectionAdapters::QueryCache
125
- ::ActiveRecord::ConnectionAdapters::QueryCache.send(:remove_method, :select_all) if ::Rails.version < '5.0.1'
126
121
 
127
122
  ::ActiveRecord::LogSubscriber.prepend(ActiveRecord::LogSubscriber)
128
123
  ::ActiveRecord::Migration.prepend(ActiveRecord::Migration)
@@ -173,6 +168,12 @@ module Switchman
173
168
  require "switchman/active_record/postgresql_adapter"
174
169
  ::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(ActiveRecord::PostgreSQLAdapter)
175
170
  end
171
+
172
+ # If Switchman::Shard wasn't loaded as of when ActiveRecord::Base initialized
173
+ # establish a connection here instead
174
+ if !Shard.instance_variable_get(:@default)
175
+ ::ActiveRecord::Base.establish_connection
176
+ end
176
177
  end
177
178
  end
178
179