active_record_shards 3.22.0 → 4.0.0.beta1

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.
@@ -1,56 +0,0 @@
1
- module ActiveRecordShards
2
- module ConnectionSwitcher
3
- # Name of the connection pool. Used by ConnectionHandler to retrieve the current connection pool.
4
- def connection_pool_name # :nodoc:
5
- name = current_shard_selection.shard_name(self)
6
-
7
- # e.g. if "production_replica" is not defined in `Configuration`, fall back to "production"
8
- if configurations[name].nil? && on_replica?
9
- current_shard_selection.shard_name(self, false)
10
- else
11
- name
12
- end
13
- end
14
-
15
- private
16
-
17
- def ensure_shard_connection
18
- establish_shard_connection unless connected_to_shard?
19
- end
20
-
21
- def establish_shard_connection
22
- name = connection_pool_name
23
- spec = configurations[name]
24
-
25
- if spec.nil?
26
- raise ActiveRecord::AdapterNotSpecified, "No database defined by #{name} in your database config. (configurations: #{configurations.keys.inspect})"
27
- end
28
-
29
- specification_cache[name] ||= begin
30
- resolver = ActiveRecordShards::ConnectionSpecification::Resolver.new configurations
31
- resolver.spec(spec)
32
- end
33
-
34
- connection_handler.establish_connection(self, specification_cache[name])
35
- end
36
-
37
- def specification_cache
38
- @@specification_cache ||= {}
39
- end
40
-
41
- # Helper method to clear global state when testing.
42
- def clear_specification_cache
43
- @@specification_cache = {}
44
- end
45
-
46
- def connection_pool_key
47
- specification_cache[connection_pool_name]
48
- end
49
-
50
- def connected_to_shard?
51
- specs_to_pools = Hash[connection_handler.connection_pool_list.map { |pool| [pool.spec, pool] }]
52
-
53
- specs_to_pools.key?(connection_pool_key)
54
- end
55
- end
56
- end
@@ -1,30 +0,0 @@
1
- module ActiveRecordShards
2
- module ConnectionSwitcher
3
- def connection_specification_name
4
- name = current_shard_selection.resolve_connection_name(sharded: is_sharded?, configurations: configurations)
5
-
6
- @_ars_connection_specification_names ||= {}
7
- unless @_ars_connection_specification_names.include?(name)
8
- unless configurations[name] || name == "primary"
9
- raise ActiveRecord::AdapterNotSpecified, "No database defined by #{name} in your database config. (configurations: #{configurations.to_h.keys.inspect})"
10
- end
11
-
12
- @_ars_connection_specification_names[name] = true
13
- end
14
-
15
- name
16
- end
17
-
18
- private
19
-
20
- def ensure_shard_connection
21
- # See if we've connected before. If not, call `#establish_connection`
22
- # so that ActiveRecord can resolve connection_specification_name to an
23
- # ARS connection.
24
- spec_name = connection_specification_name
25
-
26
- pool = connection_handler.retrieve_connection_pool(spec_name)
27
- connection_handler.establish_connection(spec_name.to_sym) if pool.nil?
28
- end
29
- end
30
- end
@@ -1,278 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveRecordShards
4
- module DefaultReplicaPatches
5
- def self.wrap_method_in_on_replica(class_method, base, method, force_on_replica: false)
6
- base_methods =
7
- if class_method
8
- base.methods + base.private_methods
9
- else
10
- base.instance_methods + base.private_instance_methods
11
- end
12
-
13
- return unless base_methods.include?(method)
14
-
15
- _, method, punctuation = method.to_s.match(/^(.*?)([\?\!]?)$/).to_a
16
- # _ALWAYS_ on replica, or only for on `on_replica_by_default = true` models?
17
- wrapper = force_on_replica ? 'force_on_replica' : 'on_replica_unless_tx'
18
- base.class_eval <<-RUBY, __FILE__, __LINE__ + 1
19
- #{class_method ? 'class << self' : ''}
20
- def #{method}_with_default_replica#{punctuation}(*args, &block)
21
- #{wrapper} do
22
- #{method}_without_default_replica#{punctuation}(*args, &block)
23
- end
24
- end
25
-
26
- alias_method :#{method}_without_default_replica#{punctuation}, :#{method}#{punctuation}
27
- alias_method :#{method}#{punctuation}, :#{method}_with_default_replica#{punctuation}
28
- #{class_method ? 'end' : ''}
29
- RUBY
30
- end
31
-
32
- def self.wrap_method_in_on_slave(*args)
33
- ActiveRecordShards::Deprecation.deprecation_warning(
34
- :'self.wrap_method_in_on_slave',
35
- :'self.wrap_method_in_on_replica'
36
- )
37
- wrap_method_in_on_replica(*args)
38
- end
39
-
40
- def transaction_with_replica_off(*args, &block)
41
- if on_replica_by_default?
42
- begin
43
- old_val = Thread.current[:_active_record_shards_in_tx]
44
- Thread.current[:_active_record_shards_in_tx] = true
45
- transaction_without_replica_off(*args, &block)
46
- ensure
47
- Thread.current[:_active_record_shards_in_tx] = old_val
48
- end
49
- else
50
- transaction_without_replica_off(*args, &block)
51
- end
52
- end
53
- alias_method :transaction_with_slave_off, :transaction_with_replica_off
54
-
55
- module InstanceMethods
56
- # fix ActiveRecord to do the right thing, and use our aliased quote_value
57
- def quote_value(*args, &block)
58
- self.class.quote_value(*args, &block)
59
- end
60
-
61
- def on_replica_unless_tx
62
- self.class.on_replica_unless_tx { yield }
63
- end
64
- end
65
-
66
- CLASS_REPLICA_METHODS = [
67
- :calculate,
68
- :count_by_sql,
69
- :exists?,
70
- :find,
71
- :find_by,
72
- :find_by_sql,
73
- :find_every,
74
- :find_one,
75
- :find_some,
76
- :get_primary_key
77
- ].freeze
78
-
79
- CLASS_FORCE_REPLICA_METHODS = [
80
- :replace_bind_variable,
81
- :replace_bind_variables,
82
- :sanitize_sql_array,
83
- :sanitize_sql_hash_for_assignment,
84
- :table_exists?
85
- ].freeze
86
-
87
- CLASS_SLAVE_METHODS = CLASS_REPLICA_METHODS
88
- CLASS_FORCE_SLAVE_METHODS = CLASS_FORCE_REPLICA_METHODS
89
-
90
- def self.extended(base)
91
- CLASS_REPLICA_METHODS.each { |m| ActiveRecordShards::DefaultReplicaPatches.wrap_method_in_on_replica(true, base, m) }
92
- CLASS_FORCE_REPLICA_METHODS.each { |m| ActiveRecordShards::DefaultReplicaPatches.wrap_method_in_on_replica(true, base, m, force_on_replica: true) }
93
-
94
- if ActiveRecord::VERSION::MAJOR >= 5
95
- ActiveRecordShards::DefaultReplicaPatches.wrap_method_in_on_replica(true, base, :load_schema!, force_on_replica: true)
96
- else
97
- ActiveRecordShards::DefaultReplicaPatches.wrap_method_in_on_replica(true, base, :columns, force_on_replica: true)
98
- end
99
-
100
- ActiveRecordShards::DefaultReplicaPatches.wrap_method_in_on_replica(false, base, :reload)
101
-
102
- base.class_eval do
103
- include InstanceMethods
104
-
105
- class << self
106
- alias_method :transaction_without_replica_off, :transaction
107
- alias_method :transaction, :transaction_with_replica_off
108
- end
109
- end
110
- end
111
-
112
- def on_replica_unless_tx(&block)
113
- return yield if Thread.current[:_active_record_shards_in_migration]
114
- return yield if Thread.current[:_active_record_shards_in_tx]
115
-
116
- if on_replica_by_default?
117
- on_replica(&block)
118
- else
119
- yield
120
- end
121
- end
122
- alias_method :on_slave_unless_tx, :on_replica_unless_tx
123
-
124
- def force_on_replica(&block)
125
- return yield if Thread.current[:_active_record_shards_in_migration]
126
-
127
- on_cx_switch_block(:replica, construct_ro_scope: false, force: true, &block)
128
- end
129
-
130
- module ActiveRelationPatches
131
- def self.included(base)
132
- [:calculate, :exists?, :pluck, :load].each do |m|
133
- ActiveRecordShards::DefaultReplicaPatches.wrap_method_in_on_replica(false, base, m)
134
- end
135
-
136
- if ActiveRecord::VERSION::MAJOR == 4
137
- # `where` and `having` clauses call `create_binds`, which will use the primary connection
138
- ActiveRecordShards::DefaultReplicaPatches.wrap_method_in_on_replica(false, base, :create_binds, force_on_replica: true)
139
- end
140
-
141
- ActiveRecordShards::DefaultReplicaPatches.wrap_method_in_on_replica(false, base, :to_sql, force_on_replica: true)
142
- end
143
-
144
- def on_replica_unless_tx
145
- @klass.on_replica_unless_tx { yield }
146
- end
147
- end
148
-
149
- module Rails52RelationPatches
150
- def connection
151
- return super if Thread.current[:_active_record_shards_in_migration]
152
- return super if Thread.current[:_active_record_shards_in_tx]
153
-
154
- if @klass.on_replica_by_default?
155
- @klass.on_replica.connection
156
- else
157
- super
158
- end
159
- end
160
- end
161
-
162
- # in rails 4.1+, they create a join class that's used to pull in records for HABTM.
163
- # this simplifies the hell out of our existence, because all we have to do is inerit on-replica-by-default
164
- # down from the parent now.
165
- module Rails41HasAndBelongsToManyBuilderExtension
166
- def self.included(base)
167
- base.class_eval do
168
- alias_method :through_model_without_inherit_default_replica_from_lhs, :through_model
169
- alias_method :through_model, :through_model_with_inherit_default_replica_from_lhs
170
- end
171
- end
172
-
173
- def through_model_with_inherit_default_replica_from_lhs
174
- model = through_model_without_inherit_default_replica_from_lhs
175
- def model.on_replica_by_default?
176
- left_reflection.klass.on_replica_by_default?
177
- end
178
-
179
- # also transfer the sharded-ness of the left table to the join model
180
- model.not_sharded unless model.left_reflection.klass.is_sharded?
181
- model
182
- end
183
- end
184
-
185
- module AssociationsAssociationAssociationScopePatch
186
- def association_scope
187
- if klass
188
- on_replica_unless_tx { super }
189
- else
190
- super
191
- end
192
- end
193
-
194
- def on_replica_unless_tx
195
- klass.on_replica_unless_tx { yield }
196
- end
197
- end
198
-
199
- module AssociationsAssociationFindTargetPatch
200
- def find_target
201
- if klass
202
- on_replica_unless_tx { super }
203
- else
204
- super
205
- end
206
- end
207
-
208
- def on_replica_unless_tx
209
- klass.on_replica_unless_tx { yield }
210
- end
211
- end
212
-
213
- module AssociationsAssociationGetRecordsPatch
214
- def get_records # rubocop:disable Naming/AccessorMethodName
215
- if klass
216
- on_replica_unless_tx { super }
217
- else
218
- super
219
- end
220
- end
221
-
222
- def on_replica_unless_tx
223
- klass.on_replica_unless_tx { yield }
224
- end
225
- end
226
-
227
- module AssociationsPreloaderAssociationAssociatedRecordsByOwnerPatch
228
- def associated_records_by_owner(preloader)
229
- if klass
230
- on_replica_unless_tx { super }
231
- else
232
- super
233
- end
234
- end
235
-
236
- def on_replica_unless_tx
237
- klass.on_replica_unless_tx { yield }
238
- end
239
- end
240
-
241
- module AssociationsPreloaderAssociationLoadRecordsPatch
242
- def load_records
243
- if klass
244
- on_replica_unless_tx { super }
245
- else
246
- super
247
- end
248
- end
249
-
250
- def on_replica_unless_tx
251
- klass.on_replica_unless_tx { yield }
252
- end
253
- end
254
-
255
- module TypeCasterConnectionConnectionPatch
256
- def connection
257
- return super if Thread.current[:_active_record_shards_in_migration]
258
- return super if Thread.current[:_active_record_shards_in_tx]
259
-
260
- if @klass.on_replica_by_default?
261
- @klass.on_replica.connection
262
- else
263
- super
264
- end
265
- end
266
- end
267
-
268
- module SchemaDefinePatch
269
- def define(info, &block)
270
- old_val = Thread.current[:_active_record_shards_in_migration]
271
- Thread.current[:_active_record_shards_in_migration] = true
272
- super
273
- ensure
274
- Thread.current[:_active_record_shards_in_migration] = old_val
275
- end
276
- end
277
- end
278
- end
@@ -1,12 +0,0 @@
1
- module ActiveRecordShards
2
- class Deprecation < ActiveSupport::Deprecation
3
- # This allows us to define separate deprecation behavior for ActiveRecordShards, but defaults to
4
- # the same behavior globally configured with ActiveSupport.
5
- #
6
- # For example, this allows us to silence our own deprecation warnings in test while still being
7
- # able to fail tests for upstream deprecation warnings.
8
- def behavior
9
- @behavior ||= ActiveSupport::Deprecation.behavior
10
- end
11
- end
12
- end
@@ -1,125 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveRecord
4
- class Migrator
5
- def self.shards_migration_context
6
- if ActiveRecord::VERSION::MAJOR >= 6
7
- ActiveRecord::MigrationContext.new(ActiveRecord::Migrator.migrations_paths, ActiveRecord::SchemaMigration)
8
- elsif ActiveRecord::VERSION::STRING >= '5.2.0'
9
- ActiveRecord::MigrationContext.new(ActiveRecord::Migrator.migrations_paths)
10
- else
11
- self
12
- end
13
- end
14
-
15
- def initialize_with_sharding(*args)
16
- initialize_without_sharding(*args)
17
-
18
- # Rails creates the internal tables on the unsharded DB. We make them
19
- # manually on the sharded DBs.
20
- ActiveRecord::Base.on_all_shards do
21
- ActiveRecord::SchemaMigration.create_table
22
- if ActiveRecord::VERSION::MAJOR >= 5
23
- ActiveRecord::InternalMetadata.create_table
24
- end
25
- end
26
- end
27
- alias_method :initialize_without_sharding, :initialize
28
- alias_method :initialize, :initialize_with_sharding
29
-
30
- def run_with_sharding
31
- ActiveRecord::Base.on_shard(nil) { run_without_sharding }
32
- ActiveRecord::Base.on_all_shards { run_without_sharding }
33
- end
34
- alias_method :run_without_sharding, :run
35
- alias_method :run, :run_with_sharding
36
-
37
- def migrate_with_sharding
38
- ActiveRecord::Base.on_shard(nil) { migrate_without_sharding }
39
- ActiveRecord::Base.on_all_shards { migrate_without_sharding }
40
- end
41
- alias_method :migrate_without_sharding, :migrate
42
- alias_method :migrate, :migrate_with_sharding
43
-
44
- # don't allow Migrator class to cache versions
45
- undef migrated
46
- def migrated
47
- self.class.shards_migration_context.get_all_versions
48
- end
49
-
50
- # list of pending migrations is any migrations that haven't run on all shards.
51
- undef pending_migrations
52
- def pending_migrations
53
- pending, _missing = self.class.shard_status(migrations.map(&:version))
54
- pending = pending.values.flatten
55
- migrations.select { |m| pending.include?(m.version) }
56
- end
57
-
58
- # public
59
- # list of pending and missing versions per shard
60
- # [{1 => [1234567]}, {1 => [2345678]}]
61
- def self.shard_status(versions)
62
- pending = {}
63
- missing = {}
64
-
65
- collect = lambda do |shard|
66
- migrated = shards_migration_context.get_all_versions
67
-
68
- p = versions - migrated
69
- pending[shard] = p if p.any?
70
-
71
- m = migrated - versions
72
- missing[shard] = m if m.any?
73
- end
74
-
75
- ActiveRecord::Base.on_shard(nil) { collect.call(nil) }
76
- ActiveRecord::Base.on_all_shards { |shard| collect.call(shard) }
77
-
78
- [pending, missing]
79
- end
80
- end
81
- end
82
-
83
- module ActiveRecordShards
84
- module MigrationClassExtension
85
- attr_accessor :migration_shard
86
-
87
- def shard(arg = nil)
88
- self.migration_shard = arg
89
- end
90
- end
91
-
92
- module ActualMigrationExtension
93
- def migrate_with_forced_shard(direction)
94
- if migration_shard.blank?
95
- raise "#{name}: Can't run migrations without a shard spec: this may be :all, :none,
96
- or a specific shard (for data-fixups). please call shard(arg) in your migration."
97
- end
98
-
99
- shard = ActiveRecord::Base.current_shard_selection.shard
100
-
101
- if shard.nil?
102
- return if migration_shard != :none
103
- else
104
- return if migration_shard == :none
105
- return if migration_shard != :all && migration_shard.to_s != shard.to_s
106
- end
107
-
108
- migrate_without_forced_shard(direction)
109
- end
110
-
111
- def migration_shard
112
- self.class.migration_shard
113
- end
114
- end
115
- end
116
-
117
- ActiveRecord::Migration.class_eval do
118
- extend ActiveRecordShards::MigrationClassExtension
119
- include ActiveRecordShards::ActualMigrationExtension
120
-
121
- alias_method :migrate_without_forced_shard, :migrate
122
- alias_method :migrate, :migrate_with_forced_shard
123
- end
124
-
125
- ActiveRecord::MigrationProxy.delegate :migration_shard, to: :migration
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'active_record_shards/connection_pool'
4
- require 'active_record_shards/connection_handler'
5
- require 'active_record_shards/connection_specification'
6
-
7
- ActiveRecordShards::ConnectionSpecification = ActiveRecord::ConnectionAdapters::ConnectionSpecification
8
- methods_to_override = [:establish_connection, :remove_connection, :pool_for, :pool_from_any_process_for]
9
- ActiveRecordShards.override_connection_handler_methods(methods_to_override)
@@ -1,42 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveRecordShards
4
- module SchemaDumperExtension
5
- def dump(stream)
6
- stream = super(stream)
7
- original_connection = @connection
8
-
9
- if ActiveRecord::Base.supports_sharding?
10
- ActiveRecord::Base.on_first_shard do
11
- @connection = ActiveRecord::Base.connection
12
- shard_header(stream)
13
- extensions(stream)
14
- tables(stream)
15
- shard_trailer(stream)
16
- end
17
- end
18
-
19
- stream
20
- ensure
21
- @connection = original_connection
22
- end
23
-
24
- def shard_header(stream)
25
- define_params = @version ? "version: #{@version}" : ""
26
-
27
- stream.puts <<~HEADER
28
-
29
-
30
- # This section generated by active_record_shards
31
-
32
- ActiveRecord::Base.on_all_shards do
33
- ActiveRecord::Schema.define(#{define_params}) do
34
-
35
- HEADER
36
- end
37
-
38
- def shard_trailer(stream)
39
- stream.puts "end\nend"
40
- end
41
- end
42
- end