active_record_shards 4.0.0.beta9 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +126 -57
- data/lib/active_record_shards/association_collection_connection_selection.rb +16 -14
- data/lib/active_record_shards/configuration_parser.rb +6 -5
- data/lib/active_record_shards/{connection_switcher_5_1.rb → connection_switcher-5-1.rb} +11 -1
- data/lib/active_record_shards/connection_switcher-6-0.rb +30 -0
- data/lib/active_record_shards/connection_switcher.rb +131 -46
- data/lib/active_record_shards/default_replica_patches.rb +237 -0
- data/lib/active_record_shards/migration.rb +124 -0
- data/lib/active_record_shards/model.rb +36 -21
- data/lib/active_record_shards/schema_dumper_extension.rb +42 -0
- data/lib/active_record_shards/shard_selection.rb +47 -7
- data/lib/active_record_shards/shard_support.rb +7 -7
- data/lib/active_record_shards/sql_comments.rb +11 -4
- data/lib/active_record_shards/tasks.rb +46 -29
- data/lib/active_record_shards.rb +59 -12
- metadata +64 -52
- data/lib/active_record_shards/base_config.rb +0 -26
- data/lib/active_record_shards/connection_resolver.rb +0 -31
- data/lib/active_record_shards/connection_switcher_5_0.rb +0 -19
- data/lib/active_record_shards/default_slave_patches.rb +0 -136
- data/lib/active_record_shards/no_shard_selection.rb +0 -18
- data/lib/active_record_shards/sharded_model.rb +0 -31
- data/lib/active_record_shards/slave_db.rb +0 -105
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 07a2ae4e9b397abcc455655266c9d66c76af160e56ea963b401a130589f3f48c
|
4
|
+
data.tar.gz: 45993e917fb0585637c72f89d6bee77f805e0192efb6ca3f476e86929d9192ab
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 16309006259fadb5b3851ea470d8859618c2df4d10555e5ecb50c257d538ac3f36c69490a33547bc7e53e6a2962d5985623176021b5f0866f9a35973138000cc
|
7
|
+
data.tar.gz: d23f2ae22180be953ba4a8bbb23000f73901ecf7950cae95858d371eec24bf01f2e7d6ee0c7225e8bbe2f76490261baae4059193718ae3cb17ffd78ab338f852
|
data/README.md
CHANGED
@@ -1,11 +1,20 @@
|
|
1
|
-
[![Build Status](https://
|
1
|
+
[![Build Status](https://github.com/zendesk/active_record_shards/workflows/CI/badge.svg)](https://github.com/zendesk/active_record_shards/actions?query=workflow%3ACI)
|
2
2
|
|
3
3
|
# ActiveRecord Shards
|
4
4
|
|
5
|
-
ActiveRecord Shards is an extension for ActiveRecord that provides support for sharded database and
|
5
|
+
ActiveRecord Shards is an extension for ActiveRecord that provides support for sharded database and replicas. Basically it is just a nice way to
|
6
6
|
switch between database connections. We've made the implementation very small, and have tried not to reinvent any wheels already present in ActiveRecord.
|
7
7
|
|
8
|
-
ActiveRecord Shards has been used and tested on Rails 5.0 and has in some form or another been used in production on
|
8
|
+
ActiveRecord Shards has been used and tested on Rails 5.x and 6.0, and has in some form or another been used in production on large Rails apps for several years.
|
9
|
+
|
10
|
+
- [Installation](#installation)
|
11
|
+
- [Configuration](#configuration)
|
12
|
+
- [Migrations](#migrations)
|
13
|
+
- [Example](#example)
|
14
|
+
- [Shared Model](#create-a-table-for-the-shared-not-sharded-model)
|
15
|
+
- [Sharded Model](#create-a-table-for-the-sharded-model)
|
16
|
+
- [Usage](#usage)
|
17
|
+
- [Debugging](#debugging)
|
9
18
|
|
10
19
|
## Installation
|
11
20
|
|
@@ -15,83 +24,143 @@ and make sure to require 'active\_record\_shards' in some way.
|
|
15
24
|
|
16
25
|
## Configuration
|
17
26
|
|
18
|
-
Add the
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
27
|
+
Add the replica and shard configuration to config/database.yml:
|
28
|
+
|
29
|
+
```yaml
|
30
|
+
production:
|
31
|
+
adapter: mysql
|
32
|
+
encoding: utf8
|
33
|
+
database: my_app_main
|
34
|
+
pool: 5
|
35
|
+
host: db1
|
36
|
+
username: root
|
37
|
+
password:
|
38
|
+
replica:
|
39
|
+
host: db1_replica
|
40
|
+
shards:
|
41
|
+
1:
|
42
|
+
host: db_shard1
|
43
|
+
database: my_app_shard
|
44
|
+
replica:
|
45
|
+
host: db_shard1_replica
|
46
|
+
2:
|
47
|
+
host: db_shard2
|
48
|
+
database: my_app_shard
|
49
|
+
replica:
|
50
|
+
host: db_shard2_replica
|
51
|
+
```
|
41
52
|
|
42
53
|
basically connections inherit configuration from the parent configuration file.
|
43
54
|
|
44
|
-
##
|
55
|
+
## Migrations
|
45
56
|
|
46
|
-
|
47
|
-
|
57
|
+
ActiveRecord Shards also patches migrations to support running migrations on a shared (not sharded) or a sharded database.
|
58
|
+
Each migration class has to specify a shard spec indicating where to run the migration.
|
59
|
+
|
60
|
+
Valid shard specs:
|
61
|
+
|
62
|
+
* `:none` - Run this migration on the shared database, not any shards
|
63
|
+
* `:all` - Run this migration on all of the shards, not the shared database
|
64
|
+
|
65
|
+
#### Example
|
66
|
+
|
67
|
+
###### Create a table for the shared (not sharded) model
|
48
68
|
|
49
|
-
|
50
|
-
|
69
|
+
```ruby
|
70
|
+
class CreateAccounts < ActiveRecord::Migration
|
71
|
+
shard :none
|
72
|
+
|
73
|
+
def change
|
74
|
+
create_table :accounts do |t|
|
75
|
+
# This is NOT necessary for the gem to work, we just use it in the examples below demonstrating one way to switch shards
|
76
|
+
t.integer :shard_id, null: false
|
77
|
+
|
78
|
+
t.string :name
|
51
79
|
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
```
|
83
|
+
|
84
|
+
###### Create a table for the sharded model
|
52
85
|
|
53
|
-
|
54
|
-
|
86
|
+
```ruby
|
87
|
+
class CreateProjects < ActiveRecord::Migration
|
88
|
+
shard :all
|
89
|
+
|
90
|
+
def change
|
91
|
+
create_table :projects do |t|
|
92
|
+
t.references :account
|
93
|
+
t.string :name
|
55
94
|
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
```
|
98
|
+
|
99
|
+
## Usage
|
100
|
+
|
101
|
+
Normally you have some models that live on a shared database, and you might need to query this data in order to know what shard to switch to.
|
102
|
+
All the models that live on the shared database must be marked as not\_sharded:
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
class Account < ActiveRecord::Base
|
106
|
+
not_sharded
|
107
|
+
|
108
|
+
has_many :projects
|
109
|
+
end
|
110
|
+
|
111
|
+
class Project < ActiveRecord::Base
|
112
|
+
belongs_to :account
|
113
|
+
end
|
114
|
+
```
|
56
115
|
|
57
116
|
So in this setup the accounts live on the shared database, but the projects are sharded. If accounts have a shard\_id column, you could lookup the account
|
58
117
|
in a rack middleware and switch to the right shard:
|
59
118
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
def call(env)
|
66
|
-
account = lookup_account(env)
|
119
|
+
```ruby
|
120
|
+
class AccountMiddleware
|
121
|
+
def initialize(app)
|
122
|
+
@app = app
|
123
|
+
end
|
67
124
|
|
68
|
-
|
69
|
-
|
70
|
-
@app.call(env)
|
71
|
-
end
|
72
|
-
else
|
73
|
-
@app.call(env)
|
74
|
-
end
|
75
|
-
end
|
125
|
+
def call(env)
|
126
|
+
account = lookup_account(env)
|
76
127
|
|
77
|
-
|
78
|
-
|
128
|
+
if account
|
129
|
+
ActiveRecord::Base.on_shard(account.shard_id) do
|
130
|
+
@app.call(env)
|
79
131
|
end
|
132
|
+
else
|
133
|
+
@app.call(env)
|
80
134
|
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def lookup_account(env)
|
138
|
+
# ...
|
139
|
+
end
|
140
|
+
end
|
141
|
+
```
|
81
142
|
|
82
|
-
You can switch to the
|
143
|
+
You can switch to the replica databases at any point by wrapping your code in an on\_replica block:
|
83
144
|
|
84
|
-
|
85
|
-
|
86
|
-
|
145
|
+
```ruby
|
146
|
+
ActiveRecord::Base.on_replica do
|
147
|
+
Account.find_by_big_expensive_query
|
148
|
+
end
|
149
|
+
```
|
150
|
+
|
151
|
+
This will perform the query on the replica, and mark the returned instances as read-only. There is also a shortcut for this:
|
152
|
+
|
153
|
+
```ruby
|
154
|
+
Account.on_replica.find_by_big_expensive_query
|
155
|
+
```
|
87
156
|
|
88
|
-
|
157
|
+
If you do not want instances returned from replicas to be marked as read-only, this can be disabled globally:
|
89
158
|
|
90
|
-
|
159
|
+
`ActiveRecordShards.disable_replica_readonly_records = true`
|
91
160
|
|
92
161
|
## Debugging
|
93
162
|
|
94
|
-
Show if a query went to
|
163
|
+
Show if a query went to primary or replica in the logs:
|
95
164
|
|
96
165
|
```Ruby
|
97
166
|
require 'active_record_shards/sql_comments'
|
@@ -1,40 +1,42 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module ActiveRecordShards
|
3
4
|
module AssociationCollectionConnectionSelection
|
4
|
-
def
|
5
|
-
condition ?
|
5
|
+
def on_replica_if(condition)
|
6
|
+
condition ? on_replica : self
|
6
7
|
end
|
7
8
|
|
8
|
-
def
|
9
|
-
|
9
|
+
def on_replica_unless(condition)
|
10
|
+
on_replica_if(!condition)
|
10
11
|
end
|
11
12
|
|
12
|
-
def
|
13
|
-
condition ?
|
13
|
+
def on_primary_if(condition)
|
14
|
+
condition ? on_primary : self
|
14
15
|
end
|
15
16
|
|
16
|
-
def
|
17
|
-
|
17
|
+
def on_primary_unless(condition)
|
18
|
+
on_primary_if(!condition)
|
18
19
|
end
|
19
20
|
|
20
|
-
def
|
21
|
-
|
21
|
+
def on_replica
|
22
|
+
PrimaryReplicaProxy.new(self, :replica)
|
22
23
|
end
|
23
24
|
|
24
|
-
def
|
25
|
-
|
25
|
+
def on_primary
|
26
|
+
PrimaryReplicaProxy.new(self, :primary)
|
26
27
|
end
|
27
28
|
|
28
|
-
class
|
29
|
+
class PrimaryReplicaProxy
|
29
30
|
def initialize(association_collection, which)
|
30
31
|
@association_collection = association_collection
|
31
32
|
@which = which
|
32
33
|
end
|
33
34
|
|
34
|
-
def method_missing(method, *args, &block) # rubocop:disable Style/
|
35
|
+
def method_missing(method, *args, &block) # rubocop:disable Style/MethodMissingSuper, Style/MissingRespondToMissing
|
35
36
|
reflection = @association_collection.proxy_association.reflection
|
36
37
|
reflection.klass.on_cx_switch_block(@which) { @association_collection.send(method, *args, &block) }
|
37
38
|
end
|
39
|
+
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
|
38
40
|
end
|
39
41
|
end
|
40
42
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'active_support/core_ext'
|
3
4
|
|
4
5
|
module ActiveRecordShards
|
@@ -6,7 +7,7 @@ module ActiveRecordShards
|
|
6
7
|
module_function
|
7
8
|
|
8
9
|
def explode(conf)
|
9
|
-
conf = conf.deep_dup
|
10
|
+
conf = conf.to_h.deep_dup
|
10
11
|
|
11
12
|
conf.to_a.each do |env_name, env_config|
|
12
13
|
next unless shards = env_config.delete('shards')
|
@@ -23,9 +24,9 @@ module ActiveRecordShards
|
|
23
24
|
end
|
24
25
|
|
25
26
|
conf.to_a.each do |env_name, env_config|
|
26
|
-
if
|
27
|
-
expand_child!(env_config,
|
28
|
-
conf["#{env_name}
|
27
|
+
if replica_conf = env_config.delete('replica')
|
28
|
+
expand_child!(env_config, replica_conf)
|
29
|
+
conf["#{env_name}_replica"] = replica_conf
|
29
30
|
end
|
30
31
|
end
|
31
32
|
|
@@ -34,7 +35,7 @@ module ActiveRecordShards
|
|
34
35
|
|
35
36
|
def expand_child!(parent, child)
|
36
37
|
parent.each do |key, value|
|
37
|
-
unless ['
|
38
|
+
unless ['replica', 'shards'].include?(key) || value.is_a?(Hash)
|
38
39
|
child[key] ||= value
|
39
40
|
end
|
40
41
|
end
|
@@ -1,5 +1,15 @@
|
|
1
1
|
module ActiveRecordShards
|
2
|
-
module
|
2
|
+
module ConnectionSwitcher
|
3
|
+
def connection_specification_name
|
4
|
+
name = current_shard_selection.resolve_connection_name(sharded: is_sharded?, configurations: configurations)
|
5
|
+
|
6
|
+
unless configurations[name] || name == "primary"
|
7
|
+
raise ActiveRecord::AdapterNotSpecified, "No database defined by #{name} in your database config. (configurations: #{configurations.to_h.keys.inspect})"
|
8
|
+
end
|
9
|
+
|
10
|
+
name
|
11
|
+
end
|
12
|
+
|
3
13
|
private
|
4
14
|
|
5
15
|
def ensure_shard_connection
|
@@ -0,0 +1,30 @@
|
|
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,9 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'active_record_shards/shard_support'
|
3
4
|
|
4
5
|
module ActiveRecordShards
|
5
6
|
module ConnectionSwitcher
|
6
|
-
SHARD_NAMES_CONFIG_KEY = 'shard_names'
|
7
|
+
SHARD_NAMES_CONFIG_KEY = 'shard_names'
|
7
8
|
|
8
9
|
def self.extended(base)
|
9
10
|
base.singleton_class.send(:alias_method, :load_schema_without_default_shard!, :load_schema!)
|
@@ -13,17 +14,26 @@ module ActiveRecordShards
|
|
13
14
|
base.singleton_class.send(:alias_method, :table_exists?, :table_exists_with_default_shard?)
|
14
15
|
end
|
15
16
|
|
17
|
+
def default_shard=(new_default_shard)
|
18
|
+
ActiveRecordShards::ShardSelection.default_shard = new_default_shard
|
19
|
+
switch_connection(shard: new_default_shard)
|
20
|
+
end
|
21
|
+
|
22
|
+
def on_primary_db(&block)
|
23
|
+
on_shard(nil, &block)
|
24
|
+
end
|
25
|
+
|
16
26
|
def on_shard(shard)
|
17
|
-
|
18
|
-
switch_connection(
|
27
|
+
old_options = current_shard_selection.options
|
28
|
+
switch_connection(shard: shard) if supports_sharding?
|
19
29
|
yield
|
20
30
|
ensure
|
21
|
-
switch_connection(
|
31
|
+
switch_connection(old_options)
|
22
32
|
end
|
23
33
|
|
24
|
-
def on_first_shard
|
34
|
+
def on_first_shard(&block)
|
25
35
|
shard_name = shard_names.first
|
26
|
-
on_shard(shard_name)
|
36
|
+
on_shard(shard_name, &block)
|
27
37
|
end
|
28
38
|
|
29
39
|
def shards
|
@@ -31,85 +41,144 @@ module ActiveRecordShards
|
|
31
41
|
end
|
32
42
|
|
33
43
|
def on_all_shards
|
34
|
-
|
44
|
+
old_options = current_shard_selection.options
|
35
45
|
if supports_sharding?
|
36
46
|
shard_names.map do |shard|
|
37
|
-
switch_connection(
|
47
|
+
switch_connection(shard: shard)
|
38
48
|
yield(shard)
|
39
49
|
end
|
40
50
|
else
|
41
51
|
[yield]
|
42
52
|
end
|
43
53
|
ensure
|
44
|
-
switch_connection(
|
54
|
+
switch_connection(old_options)
|
55
|
+
end
|
56
|
+
|
57
|
+
def on_replica_if(condition, &block)
|
58
|
+
condition ? on_replica(&block) : yield
|
45
59
|
end
|
46
60
|
|
47
|
-
def
|
48
|
-
|
61
|
+
def on_replica_unless(condition, &block)
|
62
|
+
on_replica_if(!condition, &block)
|
63
|
+
end
|
64
|
+
|
65
|
+
def on_primary_if(condition, &block)
|
66
|
+
condition ? on_primary(&block) : yield
|
67
|
+
end
|
68
|
+
|
69
|
+
def on_primary_unless(condition, &block)
|
70
|
+
on_primary_if(!condition, &block)
|
71
|
+
end
|
72
|
+
|
73
|
+
def on_primary_or_replica(which, &block)
|
74
|
+
if block_given?
|
75
|
+
on_cx_switch_block(which, &block)
|
76
|
+
else
|
77
|
+
PrimaryReplicaProxy.new(self, which)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Executes queries using the replica database. Fails over to primary if no replica is found.
|
82
|
+
# if you want to execute a block of code on the replica you can go:
|
83
|
+
# Account.on_replica do
|
84
|
+
# Account.first
|
85
|
+
# end
|
86
|
+
# the first account will be found on the replica DB
|
87
|
+
#
|
88
|
+
# For one-liners you can simply do
|
89
|
+
# Account.on_replica.first
|
90
|
+
def on_replica(&block)
|
91
|
+
on_primary_or_replica(:replica, &block)
|
92
|
+
end
|
93
|
+
|
94
|
+
def on_primary(&block)
|
95
|
+
on_primary_or_replica(:primary, &block)
|
96
|
+
end
|
97
|
+
|
98
|
+
def on_cx_switch_block(which, force: false, construct_ro_scope: nil, &block)
|
99
|
+
@disallow_replica ||= 0
|
100
|
+
@disallow_replica += 1 if which == :primary
|
101
|
+
|
102
|
+
switch_to_replica = force || @disallow_replica.zero?
|
103
|
+
old_options = current_shard_selection.options
|
104
|
+
|
105
|
+
switch_connection(replica: switch_to_replica)
|
106
|
+
|
107
|
+
# we avoid_readonly_scope to prevent some stack overflow problems, like when
|
108
|
+
# .columns calls .with_scope which calls .columns and onward, endlessly.
|
109
|
+
if self == ActiveRecord::Base || !switch_to_replica || construct_ro_scope == false || ActiveRecordShards.disable_replica_readonly_records == true
|
110
|
+
yield
|
111
|
+
else
|
112
|
+
readonly.scoping(&block)
|
113
|
+
end
|
114
|
+
ensure
|
115
|
+
@disallow_replica -= 1 if which == :primary
|
116
|
+
switch_connection(old_options) if old_options
|
49
117
|
end
|
50
118
|
|
51
119
|
def supports_sharding?
|
52
120
|
shard_names.any?
|
53
121
|
end
|
54
122
|
|
55
|
-
def
|
56
|
-
|
123
|
+
def on_replica?
|
124
|
+
current_shard_selection.on_replica?
|
57
125
|
end
|
58
126
|
|
59
|
-
def current_shard_selection
|
60
|
-
Thread.current[:shard_selection]
|
127
|
+
def current_shard_selection
|
128
|
+
Thread.current[:shard_selection] ||= ShardSelection.new
|
61
129
|
end
|
62
130
|
|
63
131
|
def current_shard_id
|
64
132
|
current_shard_selection.shard
|
65
133
|
end
|
66
134
|
|
67
|
-
def on_shard?
|
68
|
-
current_shard_selection.on_shard?
|
69
|
-
end
|
70
|
-
|
71
135
|
def shard_names
|
72
|
-
unless
|
73
|
-
raise "
|
74
|
-
end
|
75
|
-
unless config[SHARD_NAMES_CONFIG_KEY]
|
76
|
-
raise "No shards configured for #{shard_env}"
|
77
|
-
end
|
78
|
-
unless config[SHARD_NAMES_CONFIG_KEY].all? { |shard_name| shard_name.is_a?(Integer) }
|
79
|
-
raise "All shard names must be integers: #{config[SHARD_NAMES_CONFIG_KEY].inspect}."
|
136
|
+
unless config_for_env.fetch(SHARD_NAMES_CONFIG_KEY, []).all? { |shard_name| shard_name.is_a?(Integer) }
|
137
|
+
raise "All shard names must be integers: #{config_for_env[SHARD_NAMES_CONFIG_KEY].inspect}."
|
80
138
|
end
|
81
|
-
config[SHARD_NAMES_CONFIG_KEY]
|
82
|
-
end
|
83
139
|
|
84
|
-
|
85
|
-
with_default_shard { table_exists_without_default_shard? }
|
140
|
+
config_for_env[SHARD_NAMES_CONFIG_KEY] || []
|
86
141
|
end
|
87
142
|
|
88
143
|
private
|
89
144
|
|
90
|
-
def
|
91
|
-
|
145
|
+
def config_for_env
|
146
|
+
@_ars_config_for_env ||= {}
|
147
|
+
@_ars_config_for_env[shard_env] ||= begin
|
92
148
|
unless config = configurations[shard_env]
|
93
|
-
raise "Did not find #{shard_env} in configurations, did you forget to add it to your database config? (configurations: #{configurations.inspect})"
|
149
|
+
raise "Did not find #{shard_env} in configurations, did you forget to add it to your database config? (configurations: #{configurations.to_h.keys.inspect})"
|
94
150
|
end
|
95
|
-
|
96
|
-
|
151
|
+
|
152
|
+
config
|
153
|
+
end
|
154
|
+
end
|
155
|
+
alias_method :check_config_for_env, :config_for_env
|
156
|
+
|
157
|
+
def switch_connection(options)
|
158
|
+
if options.any?
|
159
|
+
if options.key?(:replica)
|
160
|
+
current_shard_selection.on_replica = options[:replica]
|
161
|
+
end
|
162
|
+
|
163
|
+
if options.key?(:shard)
|
164
|
+
check_config_for_env
|
165
|
+
|
166
|
+
current_shard_selection.shard = options[:shard]
|
97
167
|
end
|
98
|
-
self.current_shard_selection = selection
|
99
168
|
|
100
169
|
ensure_shard_connection
|
101
|
-
else
|
102
|
-
self.current_shard_selection = selection
|
103
170
|
end
|
104
171
|
end
|
105
172
|
|
106
173
|
def shard_env
|
107
|
-
ActiveRecordShards.
|
174
|
+
ActiveRecordShards.app_env
|
108
175
|
end
|
109
176
|
|
110
|
-
|
111
|
-
|
112
|
-
|
177
|
+
# Make these few schema related methods available before having switched to
|
178
|
+
# a shard.
|
179
|
+
def with_default_shard(&block)
|
180
|
+
if is_sharded? && current_shard_id.nil? && table_name != ActiveRecord::SchemaMigration.table_name
|
181
|
+
on_first_shard(&block)
|
113
182
|
else
|
114
183
|
yield
|
115
184
|
end
|
@@ -118,14 +187,30 @@ module ActiveRecordShards
|
|
118
187
|
def load_schema_with_default_shard!
|
119
188
|
with_default_shard { load_schema_without_default_shard! }
|
120
189
|
end
|
190
|
+
|
191
|
+
def table_exists_with_default_shard?
|
192
|
+
with_default_shard { table_exists_without_default_shard? }
|
193
|
+
end
|
194
|
+
|
195
|
+
class PrimaryReplicaProxy
|
196
|
+
def initialize(target, which)
|
197
|
+
@target = target
|
198
|
+
@which = which
|
199
|
+
end
|
200
|
+
|
201
|
+
def method_missing(method, *args, &block) # rubocop:disable Style/MethodMissingSuper, Style/MissingRespondToMissing
|
202
|
+
@target.on_primary_or_replica(@which) { @target.send(method, *args, &block) }
|
203
|
+
end
|
204
|
+
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
|
205
|
+
end
|
121
206
|
end
|
122
207
|
end
|
123
208
|
|
124
209
|
case "#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}"
|
125
|
-
when '5.0'
|
126
|
-
require 'active_record_shards/connection_switcher_5_0'
|
127
210
|
when '5.1', '5.2'
|
128
|
-
require 'active_record_shards/
|
211
|
+
require 'active_record_shards/connection_switcher-5-1'
|
212
|
+
when '6.0'
|
213
|
+
require 'active_record_shards/connection_switcher-6-0'
|
129
214
|
else
|
130
215
|
raise "ActiveRecordShards is not compatible with #{ActiveRecord::VERSION::STRING}"
|
131
216
|
end
|