lhm-shopify 4.4.1 → 4.5.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.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +5 -3
- data/.ruby-version +1 -1
- data/Appraisals +4 -10
- data/CHANGELOG.md +10 -0
- data/Gemfile.lock +25 -17
- data/README.md +30 -1
- data/gemfiles/{activerecord_6.1.gemfile → activerecord_7.2.gemfile} +1 -2
- data/gemfiles/{activerecord_6.1.gemfile.lock → activerecord_7.2.gemfile.lock} +36 -22
- data/gemfiles/{activerecord_7.1.gemfile → activerecord_8.0.gemfile} +1 -1
- data/gemfiles/{activerecord_7.1.gemfile.lock → activerecord_8.0.gemfile.lock} +31 -22
- data/gemfiles/activerecord_head.gemfile.lock +27 -19
- data/lhm.gemspec +2 -0
- data/lib/lhm/intersection.rb +3 -2
- data/lib/lhm/invoker.rb +8 -7
- data/lib/lhm/migration.rb +3 -2
- data/lib/lhm/migrator.rb +25 -10
- data/lib/lhm/sql_retry.rb +1 -0
- data/lib/lhm/throttler/replica_lag.rb +1 -18
- data/lib/lhm/version.rb +1 -1
- data/lib/lhm.rb +2 -2
- data/spec/fixtures/myisam_users.ddl +10 -0
- data/spec/integration/lhm_spec.rb +95 -1
- data/spec/integration/sql_retry/db_connection_helper.rb +1 -1
- data/spec/integration/sql_retry/lock_wait_spec.rb +4 -2
- data/spec/unit/migrator_spec.rb +32 -2
- data/spec/unit/throttler/replica_lag_spec.rb +1 -5
- metadata +36 -12
- data/gemfiles/activerecord_7.0.gemfile +0 -8
- data/gemfiles/activerecord_7.0.gemfile.lock +0 -74
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 56380d3c695085c3fdc5ea625014ad7a90948a0a227d9f543cfc23efc8ec9403
|
|
4
|
+
data.tar.gz: b06d43bb5c314e15846ee46ed50acfe8a076069558b0fd8f94a620a574bd2a38
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0244a6df042163ed02c77dd37bfaa800ff99a8a5af518561694fb071eb630dafd73cc12146be38a41e1e02d1f5523f7ae9ebcbd90a9696ae267d2b1e5e425957
|
|
7
|
+
data.tar.gz: 4fda241d6c7f516d96c679c1fee6bfdabc5c13e8e27baa95623406f0fb568294271fac81a836e3611b4f9d3243ef14fa7d433693603b3ae5056fdd7a31acc66c
|
data/.github/workflows/test.yml
CHANGED
|
@@ -15,17 +15,19 @@ jobs:
|
|
|
15
15
|
fail-fast: false
|
|
16
16
|
max-parallel: 8
|
|
17
17
|
matrix:
|
|
18
|
-
activerecord: ["
|
|
18
|
+
activerecord: ["7.2", "8.0", "head"]
|
|
19
19
|
ruby: ["3.1", "3.2", "3.3", "head"]
|
|
20
20
|
mysql: ["5.7", "8.0"]
|
|
21
21
|
adapter: ["mysql2", "trilogy"]
|
|
22
22
|
exclude:
|
|
23
23
|
- activerecord: 6.1
|
|
24
24
|
ruby: head
|
|
25
|
-
- activerecord: 7.
|
|
25
|
+
- activerecord: 7.2
|
|
26
26
|
ruby: head
|
|
27
|
+
- activerecord: 8.0
|
|
28
|
+
ruby: 3.1
|
|
27
29
|
- activerecord: head
|
|
28
|
-
ruby: 3.
|
|
30
|
+
ruby: 3.1
|
|
29
31
|
|
|
30
32
|
env:
|
|
31
33
|
BUNDLE_GEMFILE: "${{ github.workspace }}/gemfiles/activerecord_${{ matrix.activerecord }}.gemfile"
|
data/.ruby-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.3.6
|
data/Appraisals
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
appraise "activerecord-
|
|
2
|
-
gem "activerecord", "
|
|
3
|
-
gem "activerecord-trilogy-adapter"
|
|
1
|
+
appraise "activerecord-7.2" do
|
|
2
|
+
gem "activerecord", "7.2.2.1"
|
|
4
3
|
end
|
|
5
4
|
|
|
6
|
-
appraise "activerecord-
|
|
7
|
-
gem "activerecord", "
|
|
8
|
-
gem "activerecord-trilogy-adapter"
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
appraise "activerecord-7.1" do
|
|
12
|
-
gem "activerecord", "7.1.1"
|
|
5
|
+
appraise "activerecord-8.0" do
|
|
6
|
+
gem "activerecord", "8.0.1"
|
|
13
7
|
end
|
|
14
8
|
|
|
15
9
|
appraise "activerecord-head" do
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Unreleased
|
|
2
2
|
|
|
3
|
+
# 4.5.0 (Feb, 2025)
|
|
4
|
+
* Update test matrix to include Rails 8
|
|
5
|
+
* Add option to force tables to use default engine
|
|
6
|
+
* Change default algorithm for add/rename/remove column operations
|
|
7
|
+
* Drop support for ActiveRecord version 6.1, as it has reached EOL
|
|
8
|
+
* Add support for tables with generated columns
|
|
9
|
+
|
|
10
|
+
# 4.4.2 (Sep, 2024)
|
|
11
|
+
* Allow caller to set the algorithm that will be used for DDL ALTER TABLE operations
|
|
12
|
+
|
|
3
13
|
# 4.4.1 (Aug, 2024)
|
|
4
14
|
* Extend max_binlog_cache_size exceeded error handling to all throttlers
|
|
5
15
|
|
data/Gemfile.lock
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
lhm-shopify (4.
|
|
4
|
+
lhm-shopify (4.5.0)
|
|
5
5
|
retriable (>= 3.0.0)
|
|
6
6
|
|
|
7
7
|
GEM
|
|
8
8
|
remote: https://rubygems.org/
|
|
9
9
|
specs:
|
|
10
|
-
activemodel (
|
|
11
|
-
activesupport (=
|
|
12
|
-
activerecord (
|
|
13
|
-
activemodel (=
|
|
14
|
-
activesupport (=
|
|
10
|
+
activemodel (8.0.1)
|
|
11
|
+
activesupport (= 8.0.1)
|
|
12
|
+
activerecord (8.0.1)
|
|
13
|
+
activemodel (= 8.0.1)
|
|
14
|
+
activesupport (= 8.0.1)
|
|
15
15
|
timeout (>= 0.4.0)
|
|
16
|
-
activesupport (
|
|
16
|
+
activesupport (8.0.1)
|
|
17
17
|
base64
|
|
18
|
+
benchmark (>= 0.3)
|
|
18
19
|
bigdecimal
|
|
19
20
|
concurrent-ruby (~> 1.0, >= 1.3.1)
|
|
20
21
|
connection_pool (>= 2.2.5)
|
|
@@ -24,45 +25,50 @@ GEM
|
|
|
24
25
|
minitest (>= 5.1)
|
|
25
26
|
securerandom (>= 0.3)
|
|
26
27
|
tzinfo (~> 2.0, >= 2.0.5)
|
|
28
|
+
uri (>= 0.13.1)
|
|
27
29
|
after_do (0.4.0)
|
|
28
30
|
appraisal (2.5.0)
|
|
29
31
|
bundler
|
|
30
32
|
rake
|
|
31
33
|
thor (>= 0.14.0)
|
|
32
34
|
base64 (0.2.0)
|
|
33
|
-
|
|
35
|
+
benchmark (0.4.0)
|
|
36
|
+
bigdecimal (3.1.9)
|
|
34
37
|
byebug (11.1.3)
|
|
35
|
-
concurrent-ruby (1.3.
|
|
36
|
-
connection_pool (2.
|
|
38
|
+
concurrent-ruby (1.3.5)
|
|
39
|
+
connection_pool (2.5.0)
|
|
37
40
|
docile (1.4.1)
|
|
38
41
|
drb (2.2.1)
|
|
39
|
-
i18n (1.14.
|
|
42
|
+
i18n (1.14.7)
|
|
40
43
|
concurrent-ruby (~> 1.0)
|
|
41
|
-
logger (1.6.
|
|
42
|
-
minitest (5.25.
|
|
44
|
+
logger (1.6.5)
|
|
45
|
+
minitest (5.25.4)
|
|
43
46
|
mocha (2.4.5)
|
|
44
47
|
ruby2_keywords (>= 0.0.5)
|
|
45
48
|
mysql2 (0.5.6)
|
|
49
|
+
ostruct (0.6.1)
|
|
46
50
|
rake (13.2.1)
|
|
47
51
|
retriable (3.1.2)
|
|
48
52
|
ruby2_keywords (0.0.5)
|
|
49
|
-
securerandom (0.
|
|
53
|
+
securerandom (0.4.1)
|
|
50
54
|
simplecov (0.22.0)
|
|
51
55
|
docile (~> 1.1)
|
|
52
56
|
simplecov-html (~> 0.11)
|
|
53
57
|
simplecov_json_formatter (~> 0.1)
|
|
54
58
|
simplecov-html (0.12.3)
|
|
55
59
|
simplecov_json_formatter (0.1.4)
|
|
56
|
-
thor (1.3.
|
|
57
|
-
timeout (0.4.
|
|
60
|
+
thor (1.3.2)
|
|
61
|
+
timeout (0.4.3)
|
|
58
62
|
toxiproxy (2.0.2)
|
|
59
63
|
trilogy (2.8.1)
|
|
60
64
|
tzinfo (2.0.6)
|
|
61
65
|
concurrent-ruby (~> 1.0)
|
|
66
|
+
uri (1.0.2)
|
|
62
67
|
|
|
63
68
|
PLATFORMS
|
|
64
69
|
arm64-darwin-21
|
|
65
70
|
arm64-darwin-22
|
|
71
|
+
arm64-darwin-23
|
|
66
72
|
x86_64-darwin-20
|
|
67
73
|
x86_64-linux
|
|
68
74
|
|
|
@@ -72,13 +78,15 @@ DEPENDENCIES
|
|
|
72
78
|
appraisal
|
|
73
79
|
byebug
|
|
74
80
|
lhm-shopify!
|
|
81
|
+
logger
|
|
75
82
|
minitest
|
|
76
83
|
mocha
|
|
77
84
|
mysql2
|
|
85
|
+
ostruct
|
|
78
86
|
rake
|
|
79
87
|
simplecov
|
|
80
88
|
toxiproxy
|
|
81
89
|
trilogy
|
|
82
90
|
|
|
83
91
|
BUNDLED WITH
|
|
84
|
-
2.
|
|
92
|
+
2.6.3
|
data/README.md
CHANGED
|
@@ -207,6 +207,35 @@ Or to set that as default throttler, use the following (for instance in a Rails
|
|
|
207
207
|
Lhm.setup_throttler(:threads_running_throttler)
|
|
208
208
|
```
|
|
209
209
|
|
|
210
|
+
### Retrying chunks using Throttler
|
|
211
|
+
|
|
212
|
+
If chunks fail due to the MySQL `max_binlog_cache_size` being exceeded, the Chunker class will call the `backoff_stride` method on the throttler to reduce the stride size and retry the chunk. The default backoff factor is 0.2 (reducing the chunk size by 20% with each failure), and the minimum stride size is 1 (the stride can be reduced until each chunk is 1 row). Default values can be overridden (see below). To disable backoff, set `min_stride_size` equal to `stride_size`; this will throw an exception when `max_binlog_cache_size` is exceeded. Configuration options should be passed into the time throttler constructor or in the `throttler_options` of change_table when using a time throttler.
|
|
213
|
+
|
|
214
|
+
```ruby
|
|
215
|
+
## Configure on throttler instance
|
|
216
|
+
my_throttler = Lhm::Throttler::ThreadsRunning.new(stride: 2000, delay: 1, backoff_reduction_factor: 0.1, min_stride_size: 10)
|
|
217
|
+
|
|
218
|
+
Lhm.change_table :users, throttler: my_throttler do |m|
|
|
219
|
+
...
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
## Or pass as throttler options
|
|
223
|
+
Lhm.change_table :users, {
|
|
224
|
+
throttler: :time_throttler,
|
|
225
|
+
throttler_options: {
|
|
226
|
+
stride: 2000,
|
|
227
|
+
delay: 1,
|
|
228
|
+
backoff_reduction_factor: 0.1,
|
|
229
|
+
min_stride_size: 10
|
|
230
|
+
}
|
|
231
|
+
} do |t|
|
|
232
|
+
...
|
|
233
|
+
end
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Custom Throttlers with retry functionality
|
|
237
|
+
If using your own throttler, ensure that the class definition includes the `Lhm::Throttler::BackoffReduction` to retry failed chunks.
|
|
238
|
+
|
|
210
239
|
## Table rename strategies
|
|
211
240
|
|
|
212
241
|
There are two different table rename strategies available: `LockedSwitcher` and
|
|
@@ -311,7 +340,7 @@ open coverage/index.html
|
|
|
311
340
|
```
|
|
312
341
|
|
|
313
342
|
### Merging for a new version
|
|
314
|
-
When creating a PR for a new version, make sure that
|
|
343
|
+
When creating a PR for a new version, make sure that the version has been bumped in `lib/lhm/version.rb`. Then run the following code snippet to ensure the everything is consistent, otherwise
|
|
315
344
|
the gem will not publish.
|
|
316
345
|
```bash
|
|
317
346
|
bundle install
|
|
@@ -1,76 +1,90 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: ..
|
|
3
3
|
specs:
|
|
4
|
-
lhm-shopify (4.
|
|
4
|
+
lhm-shopify (4.5.0)
|
|
5
5
|
retriable (>= 3.0.0)
|
|
6
6
|
|
|
7
7
|
GEM
|
|
8
8
|
remote: https://rubygems.org/
|
|
9
9
|
specs:
|
|
10
|
-
activemodel (
|
|
11
|
-
activesupport (=
|
|
12
|
-
activerecord (
|
|
13
|
-
activemodel (=
|
|
14
|
-
activesupport (=
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
10
|
+
activemodel (7.2.2.1)
|
|
11
|
+
activesupport (= 7.2.2.1)
|
|
12
|
+
activerecord (7.2.2.1)
|
|
13
|
+
activemodel (= 7.2.2.1)
|
|
14
|
+
activesupport (= 7.2.2.1)
|
|
15
|
+
timeout (>= 0.4.0)
|
|
16
|
+
activesupport (7.2.2.1)
|
|
17
|
+
base64
|
|
18
|
+
benchmark (>= 0.3)
|
|
19
|
+
bigdecimal
|
|
20
|
+
concurrent-ruby (~> 1.0, >= 1.3.1)
|
|
21
|
+
connection_pool (>= 2.2.5)
|
|
22
|
+
drb
|
|
20
23
|
i18n (>= 1.6, < 2)
|
|
24
|
+
logger (>= 1.4.2)
|
|
21
25
|
minitest (>= 5.1)
|
|
22
|
-
|
|
23
|
-
|
|
26
|
+
securerandom (>= 0.3)
|
|
27
|
+
tzinfo (~> 2.0, >= 2.0.5)
|
|
24
28
|
after_do (0.4.0)
|
|
25
29
|
appraisal (2.5.0)
|
|
26
30
|
bundler
|
|
27
31
|
rake
|
|
28
32
|
thor (>= 0.14.0)
|
|
33
|
+
base64 (0.2.0)
|
|
34
|
+
benchmark (0.4.0)
|
|
35
|
+
bigdecimal (3.1.9)
|
|
29
36
|
byebug (11.1.3)
|
|
30
|
-
concurrent-ruby (1.
|
|
37
|
+
concurrent-ruby (1.3.5)
|
|
38
|
+
connection_pool (2.5.0)
|
|
31
39
|
docile (1.4.0)
|
|
32
|
-
|
|
40
|
+
drb (2.2.1)
|
|
41
|
+
i18n (1.14.7)
|
|
33
42
|
concurrent-ruby (~> 1.0)
|
|
34
|
-
|
|
43
|
+
logger (1.6.5)
|
|
44
|
+
minitest (5.25.4)
|
|
35
45
|
mocha (2.1.0)
|
|
36
46
|
ruby2_keywords (>= 0.0.5)
|
|
37
47
|
mysql2 (0.5.5)
|
|
48
|
+
ostruct (0.6.1)
|
|
38
49
|
rake (13.0.6)
|
|
39
50
|
retriable (3.1.2)
|
|
40
51
|
ruby2_keywords (0.0.5)
|
|
52
|
+
securerandom (0.4.1)
|
|
41
53
|
simplecov (0.22.0)
|
|
42
54
|
docile (~> 1.1)
|
|
43
55
|
simplecov-html (~> 0.11)
|
|
44
56
|
simplecov_json_formatter (~> 0.1)
|
|
45
57
|
simplecov-html (0.12.3)
|
|
46
58
|
simplecov_json_formatter (0.1.4)
|
|
47
|
-
thor (1.
|
|
59
|
+
thor (1.3.2)
|
|
60
|
+
timeout (0.4.3)
|
|
48
61
|
toxiproxy (2.0.2)
|
|
49
|
-
trilogy (2.
|
|
62
|
+
trilogy (2.9.0)
|
|
50
63
|
tzinfo (2.0.6)
|
|
51
64
|
concurrent-ruby (~> 1.0)
|
|
52
|
-
zeitwerk (2.6.12)
|
|
53
65
|
|
|
54
66
|
PLATFORMS
|
|
55
67
|
arm64-darwin-21
|
|
56
68
|
arm64-darwin-22
|
|
69
|
+
arm64-darwin-23
|
|
57
70
|
x86_64-darwin-20
|
|
58
71
|
x86_64-linux
|
|
59
72
|
|
|
60
73
|
DEPENDENCIES
|
|
61
|
-
activerecord (=
|
|
62
|
-
activerecord-trilogy-adapter
|
|
74
|
+
activerecord (= 7.2.2.1)
|
|
63
75
|
after_do
|
|
64
76
|
appraisal
|
|
65
77
|
byebug
|
|
66
78
|
lhm-shopify!
|
|
79
|
+
logger
|
|
67
80
|
minitest
|
|
68
81
|
mocha
|
|
69
82
|
mysql2
|
|
83
|
+
ostruct
|
|
70
84
|
rake
|
|
71
85
|
simplecov
|
|
72
86
|
toxiproxy
|
|
73
87
|
trilogy
|
|
74
88
|
|
|
75
89
|
BUNDLED WITH
|
|
76
|
-
2.
|
|
90
|
+
2.6.3
|
|
@@ -1,83 +1,92 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: ..
|
|
3
3
|
specs:
|
|
4
|
-
lhm-shopify (4.
|
|
4
|
+
lhm-shopify (4.5.0)
|
|
5
5
|
retriable (>= 3.0.0)
|
|
6
6
|
|
|
7
7
|
GEM
|
|
8
8
|
remote: https://rubygems.org/
|
|
9
9
|
specs:
|
|
10
|
-
activemodel (
|
|
11
|
-
activesupport (=
|
|
12
|
-
activerecord (
|
|
13
|
-
activemodel (=
|
|
14
|
-
activesupport (=
|
|
10
|
+
activemodel (8.0.1)
|
|
11
|
+
activesupport (= 8.0.1)
|
|
12
|
+
activerecord (8.0.1)
|
|
13
|
+
activemodel (= 8.0.1)
|
|
14
|
+
activesupport (= 8.0.1)
|
|
15
15
|
timeout (>= 0.4.0)
|
|
16
|
-
activesupport (
|
|
16
|
+
activesupport (8.0.1)
|
|
17
17
|
base64
|
|
18
|
+
benchmark (>= 0.3)
|
|
18
19
|
bigdecimal
|
|
19
|
-
concurrent-ruby (~> 1.0, >= 1.
|
|
20
|
+
concurrent-ruby (~> 1.0, >= 1.3.1)
|
|
20
21
|
connection_pool (>= 2.2.5)
|
|
21
22
|
drb
|
|
22
23
|
i18n (>= 1.6, < 2)
|
|
24
|
+
logger (>= 1.4.2)
|
|
23
25
|
minitest (>= 5.1)
|
|
24
|
-
|
|
25
|
-
tzinfo (~> 2.0)
|
|
26
|
+
securerandom (>= 0.3)
|
|
27
|
+
tzinfo (~> 2.0, >= 2.0.5)
|
|
28
|
+
uri (>= 0.13.1)
|
|
26
29
|
after_do (0.4.0)
|
|
27
30
|
appraisal (2.5.0)
|
|
28
31
|
bundler
|
|
29
32
|
rake
|
|
30
33
|
thor (>= 0.14.0)
|
|
31
|
-
base64 (0.
|
|
32
|
-
|
|
34
|
+
base64 (0.2.0)
|
|
35
|
+
benchmark (0.4.0)
|
|
36
|
+
bigdecimal (3.1.9)
|
|
33
37
|
byebug (11.1.3)
|
|
34
|
-
concurrent-ruby (1.
|
|
35
|
-
connection_pool (2.
|
|
38
|
+
concurrent-ruby (1.3.5)
|
|
39
|
+
connection_pool (2.5.0)
|
|
36
40
|
docile (1.4.0)
|
|
37
|
-
drb (2.
|
|
38
|
-
ruby2_keywords
|
|
41
|
+
drb (2.2.1)
|
|
39
42
|
i18n (1.14.1)
|
|
40
43
|
concurrent-ruby (~> 1.0)
|
|
44
|
+
logger (1.6.1)
|
|
41
45
|
minitest (5.22.2)
|
|
42
46
|
mocha (2.1.0)
|
|
43
47
|
ruby2_keywords (>= 0.0.5)
|
|
44
|
-
mutex_m (0.1.2)
|
|
45
48
|
mysql2 (0.5.5)
|
|
49
|
+
ostruct (0.6.1)
|
|
46
50
|
rake (13.0.6)
|
|
47
51
|
retriable (3.1.2)
|
|
48
52
|
ruby2_keywords (0.0.5)
|
|
53
|
+
securerandom (0.4.1)
|
|
49
54
|
simplecov (0.22.0)
|
|
50
55
|
docile (~> 1.1)
|
|
51
56
|
simplecov-html (~> 0.11)
|
|
52
57
|
simplecov_json_formatter (~> 0.1)
|
|
53
58
|
simplecov-html (0.12.3)
|
|
54
59
|
simplecov_json_formatter (0.1.4)
|
|
55
|
-
thor (1.
|
|
56
|
-
timeout (0.4.
|
|
60
|
+
thor (1.3.2)
|
|
61
|
+
timeout (0.4.3)
|
|
57
62
|
toxiproxy (2.0.2)
|
|
58
|
-
trilogy (2.
|
|
63
|
+
trilogy (2.9.0)
|
|
59
64
|
tzinfo (2.0.6)
|
|
60
65
|
concurrent-ruby (~> 1.0)
|
|
66
|
+
uri (1.0.2)
|
|
61
67
|
|
|
62
68
|
PLATFORMS
|
|
63
69
|
arm64-darwin-21
|
|
64
70
|
arm64-darwin-22
|
|
71
|
+
arm64-darwin-23
|
|
65
72
|
x86_64-darwin-20
|
|
66
73
|
x86_64-linux
|
|
67
74
|
|
|
68
75
|
DEPENDENCIES
|
|
69
|
-
activerecord (=
|
|
76
|
+
activerecord (= 8.0.1)
|
|
70
77
|
after_do
|
|
71
78
|
appraisal
|
|
72
79
|
byebug
|
|
73
80
|
lhm-shopify!
|
|
81
|
+
logger
|
|
74
82
|
minitest
|
|
75
83
|
mocha
|
|
76
84
|
mysql2
|
|
85
|
+
ostruct
|
|
77
86
|
rake
|
|
78
87
|
simplecov
|
|
79
88
|
toxiproxy
|
|
80
89
|
trilogy
|
|
81
90
|
|
|
82
91
|
BUNDLED WITH
|
|
83
|
-
2.
|
|
92
|
+
2.6.3
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
GIT
|
|
2
2
|
remote: https://github.com/rails/rails.git
|
|
3
|
-
revision:
|
|
3
|
+
revision: bdad2707835949a96dd29e747b8c324593efd4d8
|
|
4
4
|
branch: main
|
|
5
5
|
specs:
|
|
6
|
-
activemodel (8.
|
|
7
|
-
activesupport (= 8.
|
|
8
|
-
activerecord (8.
|
|
9
|
-
activemodel (= 8.
|
|
10
|
-
activesupport (= 8.
|
|
6
|
+
activemodel (8.1.0.alpha)
|
|
7
|
+
activesupport (= 8.1.0.alpha)
|
|
8
|
+
activerecord (8.1.0.alpha)
|
|
9
|
+
activemodel (= 8.1.0.alpha)
|
|
10
|
+
activesupport (= 8.1.0.alpha)
|
|
11
11
|
timeout (>= 0.4.0)
|
|
12
|
-
activesupport (8.
|
|
12
|
+
activesupport (8.1.0.alpha)
|
|
13
13
|
base64
|
|
14
|
+
benchmark (>= 0.3)
|
|
14
15
|
bigdecimal
|
|
15
16
|
concurrent-ruby (~> 1.0, >= 1.3.1)
|
|
16
17
|
connection_pool (>= 2.2.5)
|
|
@@ -20,11 +21,12 @@ GIT
|
|
|
20
21
|
minitest (>= 5.1)
|
|
21
22
|
securerandom (>= 0.3)
|
|
22
23
|
tzinfo (~> 2.0, >= 2.0.5)
|
|
24
|
+
uri (>= 0.13.1)
|
|
23
25
|
|
|
24
26
|
PATH
|
|
25
27
|
remote: ..
|
|
26
28
|
specs:
|
|
27
|
-
lhm-shopify (4.
|
|
29
|
+
lhm-shopify (4.5.0)
|
|
28
30
|
retriable (>= 3.0.0)
|
|
29
31
|
|
|
30
32
|
GEM
|
|
@@ -36,38 +38,42 @@ GEM
|
|
|
36
38
|
rake
|
|
37
39
|
thor (>= 0.14.0)
|
|
38
40
|
base64 (0.2.0)
|
|
39
|
-
|
|
41
|
+
benchmark (0.4.0)
|
|
42
|
+
bigdecimal (3.1.9)
|
|
40
43
|
byebug (11.1.3)
|
|
41
|
-
concurrent-ruby (1.3.
|
|
42
|
-
connection_pool (2.
|
|
44
|
+
concurrent-ruby (1.3.5)
|
|
45
|
+
connection_pool (2.5.0)
|
|
43
46
|
docile (1.4.1)
|
|
44
47
|
drb (2.2.1)
|
|
45
|
-
i18n (1.14.
|
|
48
|
+
i18n (1.14.7)
|
|
46
49
|
concurrent-ruby (~> 1.0)
|
|
47
|
-
logger (1.6.
|
|
48
|
-
minitest (5.
|
|
50
|
+
logger (1.6.5)
|
|
51
|
+
minitest (5.25.4)
|
|
49
52
|
mocha (2.4.5)
|
|
50
53
|
ruby2_keywords (>= 0.0.5)
|
|
51
54
|
mysql2 (0.5.6)
|
|
55
|
+
ostruct (0.6.1)
|
|
52
56
|
rake (13.2.1)
|
|
53
57
|
retriable (3.1.2)
|
|
54
58
|
ruby2_keywords (0.0.5)
|
|
55
|
-
securerandom (0.
|
|
59
|
+
securerandom (0.4.1)
|
|
56
60
|
simplecov (0.22.0)
|
|
57
61
|
docile (~> 1.1)
|
|
58
62
|
simplecov-html (~> 0.11)
|
|
59
63
|
simplecov_json_formatter (~> 0.1)
|
|
60
64
|
simplecov-html (0.12.3)
|
|
61
65
|
simplecov_json_formatter (0.1.4)
|
|
62
|
-
thor (1.3.
|
|
63
|
-
timeout (0.4.
|
|
66
|
+
thor (1.3.2)
|
|
67
|
+
timeout (0.4.3)
|
|
64
68
|
toxiproxy (2.0.2)
|
|
65
|
-
trilogy (2.
|
|
69
|
+
trilogy (2.9.0)
|
|
66
70
|
tzinfo (2.0.6)
|
|
67
71
|
concurrent-ruby (~> 1.0)
|
|
72
|
+
uri (1.0.2)
|
|
68
73
|
|
|
69
74
|
PLATFORMS
|
|
70
75
|
arm64-darwin-22
|
|
76
|
+
arm64-darwin-23
|
|
71
77
|
x86_64-linux
|
|
72
78
|
|
|
73
79
|
DEPENDENCIES
|
|
@@ -76,13 +82,15 @@ DEPENDENCIES
|
|
|
76
82
|
appraisal
|
|
77
83
|
byebug
|
|
78
84
|
lhm-shopify!
|
|
85
|
+
logger
|
|
79
86
|
minitest
|
|
80
87
|
mocha
|
|
81
88
|
mysql2
|
|
89
|
+
ostruct
|
|
82
90
|
rake
|
|
83
91
|
simplecov
|
|
84
92
|
toxiproxy
|
|
85
93
|
trilogy
|
|
86
94
|
|
|
87
95
|
BUNDLED WITH
|
|
88
|
-
2.
|
|
96
|
+
2.6.3
|
data/lhm.gemspec
CHANGED
data/lib/lhm/intersection.rb
CHANGED
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
module Lhm
|
|
5
5
|
# Determine and format columns common to origin and destination.
|
|
6
6
|
class Intersection
|
|
7
|
-
def initialize(origin, destination, renames = {})
|
|
7
|
+
def initialize(origin, destination, renames = {}, generated_column_names = [])
|
|
8
8
|
@origin = origin
|
|
9
9
|
@destination = destination
|
|
10
10
|
@renames = renames
|
|
11
|
+
@generated_column_names = generated_column_names
|
|
11
12
|
end
|
|
12
13
|
|
|
13
14
|
def origin
|
|
@@ -21,7 +22,7 @@ module Lhm
|
|
|
21
22
|
private
|
|
22
23
|
|
|
23
24
|
def common
|
|
24
|
-
(@origin.columns.keys & @destination.columns.keys).sort
|
|
25
|
+
((@origin.columns.keys & @destination.columns.keys) - @generated_column_names).sort
|
|
25
26
|
end
|
|
26
27
|
|
|
27
28
|
module Joiners
|
data/lib/lhm/invoker.rb
CHANGED
|
@@ -21,9 +21,11 @@ module Lhm
|
|
|
21
21
|
|
|
22
22
|
attr_reader :migrator, :connection
|
|
23
23
|
|
|
24
|
-
def initialize(origin, connection)
|
|
24
|
+
def initialize(origin, connection, options = {})
|
|
25
25
|
@connection = connection
|
|
26
|
-
@migrator = Migrator.new(origin, connection)
|
|
26
|
+
@migrator = Migrator.new(origin, connection, options)
|
|
27
|
+
@options = options
|
|
28
|
+
normalize_options(@options)
|
|
27
29
|
end
|
|
28
30
|
|
|
29
31
|
def set_session_lock_wait_timeouts
|
|
@@ -45,17 +47,16 @@ module Lhm
|
|
|
45
47
|
end
|
|
46
48
|
end
|
|
47
49
|
|
|
48
|
-
def run
|
|
49
|
-
normalize_options(options)
|
|
50
|
+
def run
|
|
50
51
|
set_session_lock_wait_timeouts
|
|
51
52
|
migration = @migrator.run
|
|
52
53
|
entangler = Entangler.new(migration, @connection)
|
|
53
54
|
|
|
54
55
|
entangler.run do
|
|
55
|
-
options[:verifier] ||= Proc.new { |conn| triggers_still_exist?(conn, entangler) }
|
|
56
|
-
Chunker.new(migration, @connection, options).run
|
|
56
|
+
@options[:verifier] ||= Proc.new { |conn| triggers_still_exist?(conn, entangler) }
|
|
57
|
+
Chunker.new(migration, @connection, @options).run
|
|
57
58
|
raise "Required triggers do not exist" unless triggers_still_exist?(@connection, entangler)
|
|
58
|
-
if options[:atomic_switch]
|
|
59
|
+
if @options[:atomic_switch]
|
|
59
60
|
AtomicSwitcher.new(migration, @connection).run
|
|
60
61
|
else
|
|
61
62
|
LockedSwitcher.new(migration, @connection).run
|
data/lib/lhm/migration.rb
CHANGED
|
@@ -8,12 +8,13 @@ module Lhm
|
|
|
8
8
|
class Migration
|
|
9
9
|
attr_reader :origin, :destination, :conditions, :renames
|
|
10
10
|
|
|
11
|
-
def initialize(origin, destination, conditions = nil, renames = {}, time = Time.now)
|
|
11
|
+
def initialize(origin, destination, conditions = nil, renames = {}, time = Time.now, generated_column_names = [])
|
|
12
12
|
@origin = origin
|
|
13
13
|
@destination = destination
|
|
14
14
|
@conditions = conditions
|
|
15
15
|
@renames = renames
|
|
16
16
|
@table_name = TableName.new(@origin.name, time)
|
|
17
|
+
@generated_column_names = generated_column_names
|
|
17
18
|
end
|
|
18
19
|
|
|
19
20
|
def archive_name
|
|
@@ -21,7 +22,7 @@ module Lhm
|
|
|
21
22
|
end
|
|
22
23
|
|
|
23
24
|
def intersection
|
|
24
|
-
Intersection.new(@origin, @destination, @renames)
|
|
25
|
+
Intersection.new(@origin, @destination, @renames, @generated_column_names)
|
|
25
26
|
end
|
|
26
27
|
|
|
27
28
|
def origin_name
|
data/lib/lhm/migrator.rb
CHANGED
|
@@ -15,12 +15,13 @@ module Lhm
|
|
|
15
15
|
|
|
16
16
|
attr_reader :name, :statements, :connection, :conditions, :renames, :origin
|
|
17
17
|
|
|
18
|
-
def initialize(table, connection = nil)
|
|
18
|
+
def initialize(table, connection = nil, options = {})
|
|
19
19
|
@connection = connection
|
|
20
20
|
@origin = table
|
|
21
21
|
@name = table.destination_name
|
|
22
22
|
@statements = []
|
|
23
23
|
@renames = {}
|
|
24
|
+
@options = options
|
|
24
25
|
end
|
|
25
26
|
|
|
26
27
|
# Alter a table with a custom statement
|
|
@@ -32,14 +33,16 @@ module Lhm
|
|
|
32
33
|
# end
|
|
33
34
|
#
|
|
34
35
|
# @param [String] statement SQL alter statement
|
|
36
|
+
# @param [String] algorithm Algorithm that will be used in the DDL operation
|
|
35
37
|
# @note
|
|
36
38
|
#
|
|
37
39
|
# Don't write the table name directly into the statement. Use the #name
|
|
38
40
|
# getter instead, because the alter statement will be executed against a
|
|
39
41
|
# temporary table.
|
|
40
42
|
#
|
|
41
|
-
def ddl(statement)
|
|
42
|
-
|
|
43
|
+
def ddl(statement, algorithm: nil)
|
|
44
|
+
full_statement = algorithm ? "#{statement}, ALGORITHM=#{algorithm}" : statement
|
|
45
|
+
statements << full_statement
|
|
43
46
|
end
|
|
44
47
|
|
|
45
48
|
# Add a column to a table
|
|
@@ -52,8 +55,9 @@ module Lhm
|
|
|
52
55
|
#
|
|
53
56
|
# @param [String] name Name of the column to add
|
|
54
57
|
# @param [String] definition Valid SQL column definition
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
# @param [String] algorithm Algorithm that will be used in the DDL operation
|
|
59
|
+
def add_column(name, definition, algorithm: 'COPY')
|
|
60
|
+
ddl('alter table `%s` add column `%s` %s' % [@name, name, definition], algorithm:)
|
|
57
61
|
end
|
|
58
62
|
|
|
59
63
|
# Change an existing column to a new definition
|
|
@@ -84,7 +88,8 @@ module Lhm
|
|
|
84
88
|
#
|
|
85
89
|
# @param [String] old Name of the column to change
|
|
86
90
|
# @param [String] nu New name to use for the column
|
|
87
|
-
|
|
91
|
+
# @param [String] algorithm Algorithm that will be used in the DDL operation
|
|
92
|
+
def rename_column(old, nu, algorithm: 'COPY')
|
|
88
93
|
col = @origin.columns[old.to_s]
|
|
89
94
|
|
|
90
95
|
definition = col[:type]
|
|
@@ -94,7 +99,7 @@ module Lhm
|
|
|
94
99
|
definition += " COMMENT #{@connection.quote(col[:comment])}" if col[:comment]
|
|
95
100
|
definition += " COLLATE #{@connection.quote(col[:collate])}" if col[:collate]
|
|
96
101
|
|
|
97
|
-
ddl('alter table `%s` change column `%s` `%s` %s
|
|
102
|
+
ddl('alter table `%s` change column `%s` `%s` %s' % [@name, old, nu, definition], algorithm:)
|
|
98
103
|
@renames[old.to_s] = nu.to_s
|
|
99
104
|
end
|
|
100
105
|
|
|
@@ -107,8 +112,9 @@ module Lhm
|
|
|
107
112
|
# end
|
|
108
113
|
#
|
|
109
114
|
# @param [String] name Name of the column to delete
|
|
110
|
-
|
|
111
|
-
|
|
115
|
+
# @param [String] algorithm Algorithm that will be used in the DDL operation
|
|
116
|
+
def remove_column(name, algorithm: 'COPY')
|
|
117
|
+
ddl('alter table `%s` drop `%s`' % [@name, name], algorithm:)
|
|
112
118
|
end
|
|
113
119
|
|
|
114
120
|
# Add an index to a table
|
|
@@ -210,13 +216,22 @@ module Lhm
|
|
|
210
216
|
@statements.each do |stmt|
|
|
211
217
|
@connection.execute(tagged(stmt))
|
|
212
218
|
end
|
|
213
|
-
Migration.new(@origin, destination_read, conditions, renames)
|
|
219
|
+
Migration.new(@origin, destination_read, conditions, renames, Time.now, generated_column_names)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def generated_column_names
|
|
223
|
+
@connection.columns(@origin.name).select(&:virtual?).map(&:name)
|
|
214
224
|
end
|
|
215
225
|
|
|
216
226
|
def destination_create
|
|
217
227
|
original = %{CREATE TABLE `#{ @origin.name }`}
|
|
218
228
|
replacement = %{CREATE TABLE `#{ @origin.destination_name }`}
|
|
219
229
|
stmt = @origin.ddl.gsub(original, replacement)
|
|
230
|
+
|
|
231
|
+
if @options[:force_default_engine]
|
|
232
|
+
stmt = stmt.sub(/ENGINE=\w+\s/, "")
|
|
233
|
+
end
|
|
234
|
+
|
|
220
235
|
@connection.execute(tagged(stmt))
|
|
221
236
|
|
|
222
237
|
Lhm.logger.info("Created destination table #{@origin.destination_name}")
|
data/lib/lhm/sql_retry.rb
CHANGED
|
@@ -134,7 +134,7 @@ module Lhm
|
|
|
134
134
|
raise ArgumentError, "Expected #{config_proc.inspect} to respond to `call`"
|
|
135
135
|
end
|
|
136
136
|
else
|
|
137
|
-
db_config
|
|
137
|
+
ActiveRecord::Base.connection_pool.db_config.configuration_hash.dup
|
|
138
138
|
end
|
|
139
139
|
config.deep_symbolize_keys!
|
|
140
140
|
config[:host] = @host
|
|
@@ -147,23 +147,6 @@ module Lhm
|
|
|
147
147
|
Lhm.logger.info "Unable to connect and/or query #{host}: #{e}"
|
|
148
148
|
[nil]
|
|
149
149
|
end
|
|
150
|
-
|
|
151
|
-
private
|
|
152
|
-
|
|
153
|
-
def db_config
|
|
154
|
-
if ar_supports_db_config?
|
|
155
|
-
ActiveRecord::Base.connection_pool.db_config.configuration_hash.dup
|
|
156
|
-
else
|
|
157
|
-
ActiveRecord::Base.connection_pool.spec.config.dup
|
|
158
|
-
end
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
def ar_supports_db_config?
|
|
162
|
-
# https://api.rubyonrails.org/v6.0/classes/ActiveRecord/ConnectionAdapters/ConnectionPool.html <-- has spec
|
|
163
|
-
# vs
|
|
164
|
-
# https://api.rubyonrails.org/v6.1/classes/ActiveRecord/ConnectionAdapters/ConnectionPool.html <-- has db_config
|
|
165
|
-
ActiveRecord::VERSION::MAJOR > 6 || ActiveRecord::VERSION::MAJOR == 6 && ActiveRecord::VERSION::MINOR >= 1
|
|
166
|
-
end
|
|
167
150
|
end
|
|
168
151
|
end
|
|
169
152
|
end
|
data/lib/lhm/version.rb
CHANGED
data/lib/lhm.rb
CHANGED
|
@@ -54,9 +54,9 @@ module Lhm
|
|
|
54
54
|
def change_table(table_name, options = {}, &block)
|
|
55
55
|
with_flags(options) do
|
|
56
56
|
origin = Table.parse(table_name, connection)
|
|
57
|
-
invoker = Invoker.new(origin, connection)
|
|
57
|
+
invoker = Invoker.new(origin, connection, options)
|
|
58
58
|
block.call(invoker.migrator)
|
|
59
|
-
invoker.run
|
|
59
|
+
invoker.run
|
|
60
60
|
true
|
|
61
61
|
end
|
|
62
62
|
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
CREATE TABLE `myisam_users` (
|
|
2
|
+
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
3
|
+
`reference` int(11) DEFAULT NULL,
|
|
4
|
+
`username` varchar(255) DEFAULT NULL,
|
|
5
|
+
`group` varchar(255) DEFAULT 'Superfriends',
|
|
6
|
+
`created_at` datetime DEFAULT NULL,
|
|
7
|
+
`comment` varchar(20) DEFAULT NULL,
|
|
8
|
+
`description` text,
|
|
9
|
+
PRIMARY KEY (`id`)
|
|
10
|
+
) ENGINE=MyISAM CHARSET=utf8
|
|
@@ -531,6 +531,61 @@ describe Lhm do
|
|
|
531
531
|
end
|
|
532
532
|
end
|
|
533
533
|
|
|
534
|
+
it 'works when table has generated columns' do
|
|
535
|
+
table_create(:users)
|
|
536
|
+
execute("insert into `users` set id = 1, `username` = 'memyself'")
|
|
537
|
+
execute("insert into `users` set id = 2, `username` = 'youyourself'")
|
|
538
|
+
|
|
539
|
+
# Add a generated column
|
|
540
|
+
Lhm.change_table(:users) do |t|
|
|
541
|
+
t.add_column(:sample_generated_column, 'VARCHAR(255) GENERATED ALWAYS AS (SUBSTRING(`username`, -2))')
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
# Without the handling of generated columns
|
|
545
|
+
Lhm::Migrator.any_instance.stubs(:generated_column_names).returns([])
|
|
546
|
+
# without the Migration passing in generated columns to Intersection, we observe an error as an attempt to write
|
|
547
|
+
# directly into generated columns will fail.
|
|
548
|
+
exception = assert_raises ActiveRecord::StatementInvalid do
|
|
549
|
+
Lhm.change_table(:users) do |t|
|
|
550
|
+
t.add_column(:sample_additional_column, "VARCHAR(255)")
|
|
551
|
+
end
|
|
552
|
+
end
|
|
553
|
+
assert_match "The value specified for generated column 'sample_generated_column' in table 'lhmn_users' is not allowed.", exception.message
|
|
554
|
+
|
|
555
|
+
Lhm.cleanup(true)
|
|
556
|
+
|
|
557
|
+
# With the handling of generated columns
|
|
558
|
+
Lhm::Migrator.any_instance.unstub(:generated_column_names)
|
|
559
|
+
# As we are now skipping the writing to generated columns, this migration should succeed
|
|
560
|
+
Lhm.change_table(:users) do |t|
|
|
561
|
+
t.add_column(:sample_additional_column, "VARCHAR(255)")
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
replica do
|
|
565
|
+
# new column is added
|
|
566
|
+
value(table_read(:users).columns['sample_additional_column']).must_equal({
|
|
567
|
+
:type => 'varchar(255)',
|
|
568
|
+
:is_nullable => 'YES',
|
|
569
|
+
:column_default => nil,
|
|
570
|
+
:comment => '',
|
|
571
|
+
:collate => collation,
|
|
572
|
+
})
|
|
573
|
+
|
|
574
|
+
# generated column remains intact
|
|
575
|
+
value(table_read(:users).columns['sample_generated_column']).must_equal({
|
|
576
|
+
:type => 'varchar(255)',
|
|
577
|
+
:is_nullable => 'YES',
|
|
578
|
+
:column_default => nil,
|
|
579
|
+
:comment => '',
|
|
580
|
+
:collate => collation,
|
|
581
|
+
})
|
|
582
|
+
end
|
|
583
|
+
|
|
584
|
+
result = select_one('SELECT sample_generated_column FROM users')
|
|
585
|
+
# generated column populated appropriately
|
|
586
|
+
assert_match "lf", result["sample_generated_column"]
|
|
587
|
+
end
|
|
588
|
+
|
|
534
589
|
it 'works when mysql reserved words are used' do
|
|
535
590
|
table_create(:lines)
|
|
536
591
|
execute("insert into `lines` set id = 1, `between` = 'foo'")
|
|
@@ -553,6 +608,39 @@ describe Lhm do
|
|
|
553
608
|
end
|
|
554
609
|
end
|
|
555
610
|
|
|
611
|
+
it 'creates the shadow table with the default engine when the `force_default_engine` option is used' do
|
|
612
|
+
table_create(:myisam_users)
|
|
613
|
+
|
|
614
|
+
engine = select_value("SELECT ENGINE FROM information_schema.TABLES WHERE TABLE_NAME = 'myisam_users'")
|
|
615
|
+
value(engine).must_equal("MyISAM")
|
|
616
|
+
|
|
617
|
+
Lhm.change_table(:myisam_users) do |t|
|
|
618
|
+
t.add_column(:logins, "INT(12) DEFAULT '0'", algorithm: "COPY")
|
|
619
|
+
end
|
|
620
|
+
|
|
621
|
+
engine = select_value("SELECT ENGINE FROM information_schema.TABLES WHERE TABLE_NAME = 'myisam_users'")
|
|
622
|
+
value(engine).must_equal("MyISAM")
|
|
623
|
+
|
|
624
|
+
Lhm.change_table(:myisam_users, force_default_engine: true) do |t|
|
|
625
|
+
t.remove_column(:logins)
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
engine = select_value("SELECT ENGINE FROM information_schema.TABLES WHERE TABLE_NAME = 'myisam_users'")
|
|
629
|
+
value(engine).must_equal("InnoDB")
|
|
630
|
+
end
|
|
631
|
+
|
|
632
|
+
it "should not fail using the default algorithms when changing tables with fulltext indexes" do
|
|
633
|
+
table_create(:users)
|
|
634
|
+
execute("DROP INDEX `index_with_a_custom_name` ON `users`")
|
|
635
|
+
execute("CREATE FULLTEXT INDEX `index_with_a_custom_name` ON `users` (`username`, `group`)")
|
|
636
|
+
|
|
637
|
+
Lhm.change_table(:users) do |t|
|
|
638
|
+
t.add_column(:email, "VARCHAR(255)")
|
|
639
|
+
end
|
|
640
|
+
|
|
641
|
+
value(table_read(:users).columns).must_include("email")
|
|
642
|
+
end
|
|
643
|
+
|
|
556
644
|
describe 'parallel' do
|
|
557
645
|
it 'should perserve inserts during migration' do
|
|
558
646
|
50.times { |n| execute("insert into users set reference = '#{ n }'") }
|
|
@@ -616,7 +704,13 @@ describe Lhm do
|
|
|
616
704
|
table_create(:users)
|
|
617
705
|
100.times { |n| execute("insert into users set reference = '#{ n }'") }
|
|
618
706
|
|
|
619
|
-
|
|
707
|
+
error = if ActiveRecord.version >= ::Gem::Version.new('8.1.0.alpha')
|
|
708
|
+
ActiveRecord::ConnectionNotEstablished
|
|
709
|
+
else
|
|
710
|
+
ActiveRecord::StatementInvalid
|
|
711
|
+
end
|
|
712
|
+
|
|
713
|
+
assert_raises error do
|
|
620
714
|
Toxiproxy[:mysql_master].down do
|
|
621
715
|
Lhm.change_table(:users, :atomic_switch => false) do |t|
|
|
622
716
|
t.ddl("ALTER TABLE #{t.name} CHANGE id id bigint (20) NOT NULL")
|
|
@@ -20,7 +20,7 @@ class DBConnectionHelper
|
|
|
20
20
|
:database => test_db_name,
|
|
21
21
|
:port => db_config[key]['port']
|
|
22
22
|
)
|
|
23
|
-
conn = conn.connection
|
|
23
|
+
conn = conn.respond_to?(:lease_connection) ? conn.lease_connection : conn.connection
|
|
24
24
|
init_with_dummy_data(conn) if with_data
|
|
25
25
|
conn
|
|
26
26
|
end
|
|
@@ -81,10 +81,12 @@ describe Lhm::SqlRetry do
|
|
|
81
81
|
logs = @logger.string.split("\n")
|
|
82
82
|
assert_equal 2, logs.length
|
|
83
83
|
|
|
84
|
-
assert logs.first.include?("Lock wait timeout exceeded; try restarting transaction
|
|
84
|
+
assert logs.first.include?("Lock wait timeout exceeded; try restarting transaction")
|
|
85
|
+
assert logs.first.include?("- 1 tries")
|
|
85
86
|
assert logs.first.include?("0.2 seconds until the next try")
|
|
86
87
|
|
|
87
|
-
assert logs.last.include?("Lock wait timeout exceeded; try restarting transaction
|
|
88
|
+
assert logs.last.include?("Lock wait timeout exceeded; try restarting transaction")
|
|
89
|
+
assert logs.last.include?("- 2 tries")
|
|
88
90
|
assert logs.last.include?("0.2 seconds until the next try")
|
|
89
91
|
end
|
|
90
92
|
|
data/spec/unit/migrator_spec.rb
CHANGED
|
@@ -97,7 +97,15 @@ describe Lhm::Migrator do
|
|
|
97
97
|
@creator.add_column('logins', 'INT(12)')
|
|
98
98
|
|
|
99
99
|
value(@creator.statements).must_equal([
|
|
100
|
-
'alter table `lhmn_alt` add column `logins` INT(12), ALGORITHM=
|
|
100
|
+
'alter table `lhmn_alt` add column `logins` INT(12), ALGORITHM=COPY'
|
|
101
|
+
])
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it 'should add a column using the passed algorithm' do
|
|
105
|
+
@creator.add_column('logins', 'INT(12)', algorithm: 'COPY')
|
|
106
|
+
|
|
107
|
+
value(@creator.statements).must_equal([
|
|
108
|
+
'alter table `lhmn_alt` add column `logins` INT(12), ALGORITHM=COPY'
|
|
101
109
|
])
|
|
102
110
|
end
|
|
103
111
|
|
|
@@ -105,7 +113,15 @@ describe Lhm::Migrator do
|
|
|
105
113
|
@creator.remove_column('logins')
|
|
106
114
|
|
|
107
115
|
value(@creator.statements).must_equal([
|
|
108
|
-
'alter table `lhmn_alt` drop `logins`, ALGORITHM=
|
|
116
|
+
'alter table `lhmn_alt` drop `logins`, ALGORITHM=COPY'
|
|
117
|
+
])
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
it 'should remove a column using the passed algorithm' do
|
|
121
|
+
@creator.remove_column('logins', algorithm: 'COPY')
|
|
122
|
+
|
|
123
|
+
value(@creator.statements).must_equal([
|
|
124
|
+
'alter table `lhmn_alt` drop `logins`, ALGORITHM=COPY'
|
|
109
125
|
])
|
|
110
126
|
end
|
|
111
127
|
|
|
@@ -150,6 +166,20 @@ describe Lhm::Migrator do
|
|
|
150
166
|
@creator.add_column('last', 'VARCHAR(64)')
|
|
151
167
|
value(@creator.statements.length).must_equal(2)
|
|
152
168
|
|
|
169
|
+
value(@creator.statements[0])
|
|
170
|
+
.must_equal('alter table `lhmn_alt` add column `first` VARCHAR(64), ALGORITHM=COPY')
|
|
171
|
+
|
|
172
|
+
value(@creator.statements[1])
|
|
173
|
+
.must_equal('alter table `lhmn_alt` add column `last` VARCHAR(64), ALGORITHM=COPY')
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
describe 'multiple changes using the passed algorithm' do
|
|
178
|
+
it 'should add two columns' do
|
|
179
|
+
@creator.add_column('first', 'VARCHAR(64)', algorithm: 'INPLACE')
|
|
180
|
+
@creator.add_column('last', 'VARCHAR(64)', algorithm: 'INPLACE')
|
|
181
|
+
value(@creator.statements.length).must_equal(2)
|
|
182
|
+
|
|
153
183
|
value(@creator.statements[0])
|
|
154
184
|
.must_equal('alter table `lhmn_alt` add column `first` VARCHAR(64), ALGORITHM=INPLACE')
|
|
155
185
|
|
|
@@ -66,11 +66,7 @@ describe Lhm::Throttler::Replica do
|
|
|
66
66
|
describe 'with active record config' do
|
|
67
67
|
it 'logs and creates client' do
|
|
68
68
|
active_record_config = { username: 'user', password: 'pw', database: 'db' }
|
|
69
|
-
|
|
70
|
-
ActiveRecord::Base.stubs(:connection_pool).returns(stub(db_config: stub(configuration_hash: active_record_config)))
|
|
71
|
-
else
|
|
72
|
-
ActiveRecord::Base.stubs(:connection_pool).returns(stub(spec: stub(config: active_record_config)))
|
|
73
|
-
end
|
|
69
|
+
ActiveRecord::Base.stubs(:connection_pool).returns(stub(db_config: stub(configuration_hash: active_record_config)))
|
|
74
70
|
|
|
75
71
|
DATABASE.client.stubs(:new).returns(mock())
|
|
76
72
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lhm-shopify
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 4.
|
|
4
|
+
version: 4.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- SoundCloud
|
|
@@ -9,10 +9,9 @@ authors:
|
|
|
9
9
|
- Rany Keddo
|
|
10
10
|
- Tobias Bielohlawek
|
|
11
11
|
- Tobias Schmidt
|
|
12
|
-
autorequire:
|
|
13
12
|
bindir: bin
|
|
14
13
|
cert_chain: []
|
|
15
|
-
date:
|
|
14
|
+
date: 2025-02-07 00:00:00.000000000 Z
|
|
16
15
|
dependencies:
|
|
17
16
|
- !ruby/object:Gem::Dependency
|
|
18
17
|
name: retriable
|
|
@@ -182,6 +181,34 @@ dependencies:
|
|
|
182
181
|
- - ">="
|
|
183
182
|
- !ruby/object:Gem::Version
|
|
184
183
|
version: '0'
|
|
184
|
+
- !ruby/object:Gem::Dependency
|
|
185
|
+
name: ostruct
|
|
186
|
+
requirement: !ruby/object:Gem::Requirement
|
|
187
|
+
requirements:
|
|
188
|
+
- - ">="
|
|
189
|
+
- !ruby/object:Gem::Version
|
|
190
|
+
version: '0'
|
|
191
|
+
type: :development
|
|
192
|
+
prerelease: false
|
|
193
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
194
|
+
requirements:
|
|
195
|
+
- - ">="
|
|
196
|
+
- !ruby/object:Gem::Version
|
|
197
|
+
version: '0'
|
|
198
|
+
- !ruby/object:Gem::Dependency
|
|
199
|
+
name: logger
|
|
200
|
+
requirement: !ruby/object:Gem::Requirement
|
|
201
|
+
requirements:
|
|
202
|
+
- - ">="
|
|
203
|
+
- !ruby/object:Gem::Version
|
|
204
|
+
version: '0'
|
|
205
|
+
type: :development
|
|
206
|
+
prerelease: false
|
|
207
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
208
|
+
requirements:
|
|
209
|
+
- - ">="
|
|
210
|
+
- !ruby/object:Gem::Version
|
|
211
|
+
version: '0'
|
|
185
212
|
description: Migrate large tables without downtime by copying to a temporary table
|
|
186
213
|
in chunks. The old table is not dropped. Instead, it is moved to timestamp_table_name
|
|
187
214
|
for verification.
|
|
@@ -206,12 +233,10 @@ files:
|
|
|
206
233
|
- docker-compose-mysql-5.7.yml
|
|
207
234
|
- docker-compose-mysql-8.0.yml
|
|
208
235
|
- docker-compose.yml
|
|
209
|
-
- gemfiles/
|
|
210
|
-
- gemfiles/
|
|
211
|
-
- gemfiles/
|
|
212
|
-
- gemfiles/
|
|
213
|
-
- gemfiles/activerecord_7.1.gemfile
|
|
214
|
-
- gemfiles/activerecord_7.1.gemfile.lock
|
|
236
|
+
- gemfiles/activerecord_7.2.gemfile
|
|
237
|
+
- gemfiles/activerecord_7.2.gemfile.lock
|
|
238
|
+
- gemfiles/activerecord_8.0.gemfile
|
|
239
|
+
- gemfiles/activerecord_8.0.gemfile.lock
|
|
215
240
|
- gemfiles/activerecord_head.gemfile
|
|
216
241
|
- gemfiles/activerecord_head.gemfile.lock
|
|
217
242
|
- lhm.gemspec
|
|
@@ -263,6 +288,7 @@ files:
|
|
|
263
288
|
- spec/fixtures/custom_primary_key_dest.ddl
|
|
264
289
|
- spec/fixtures/destination.ddl
|
|
265
290
|
- spec/fixtures/lines.ddl
|
|
291
|
+
- spec/fixtures/myisam_users.ddl
|
|
266
292
|
- spec/fixtures/origin.ddl
|
|
267
293
|
- spec/fixtures/permissions.ddl
|
|
268
294
|
- spec/fixtures/small_table.ddl
|
|
@@ -312,7 +338,6 @@ licenses:
|
|
|
312
338
|
- BSD-3-Clause
|
|
313
339
|
metadata:
|
|
314
340
|
allowed_push_host: https://rubygems.org
|
|
315
|
-
post_install_message:
|
|
316
341
|
rdoc_options: []
|
|
317
342
|
require_paths:
|
|
318
343
|
- lib
|
|
@@ -327,8 +352,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
327
352
|
- !ruby/object:Gem::Version
|
|
328
353
|
version: '0'
|
|
329
354
|
requirements: []
|
|
330
|
-
rubygems_version: 3.
|
|
331
|
-
signing_key:
|
|
355
|
+
rubygems_version: 3.6.3
|
|
332
356
|
specification_version: 4
|
|
333
357
|
summary: online schema changer for mysql
|
|
334
358
|
test_files: []
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
PATH
|
|
2
|
-
remote: ..
|
|
3
|
-
specs:
|
|
4
|
-
lhm-shopify (4.4.1)
|
|
5
|
-
retriable (>= 3.0.0)
|
|
6
|
-
|
|
7
|
-
GEM
|
|
8
|
-
remote: https://rubygems.org/
|
|
9
|
-
specs:
|
|
10
|
-
activemodel (7.0.8)
|
|
11
|
-
activesupport (= 7.0.8)
|
|
12
|
-
activerecord (7.0.8)
|
|
13
|
-
activemodel (= 7.0.8)
|
|
14
|
-
activesupport (= 7.0.8)
|
|
15
|
-
activerecord-trilogy-adapter (3.1.2)
|
|
16
|
-
activerecord (>= 6.0.a, < 7.1.a)
|
|
17
|
-
trilogy (>= 2.4.0)
|
|
18
|
-
activesupport (7.0.8)
|
|
19
|
-
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
20
|
-
i18n (>= 1.6, < 2)
|
|
21
|
-
minitest (>= 5.1)
|
|
22
|
-
tzinfo (~> 2.0)
|
|
23
|
-
after_do (0.4.0)
|
|
24
|
-
appraisal (2.5.0)
|
|
25
|
-
bundler
|
|
26
|
-
rake
|
|
27
|
-
thor (>= 0.14.0)
|
|
28
|
-
byebug (11.1.3)
|
|
29
|
-
concurrent-ruby (1.2.2)
|
|
30
|
-
docile (1.4.0)
|
|
31
|
-
i18n (1.14.1)
|
|
32
|
-
concurrent-ruby (~> 1.0)
|
|
33
|
-
minitest (5.22.2)
|
|
34
|
-
mocha (2.1.0)
|
|
35
|
-
ruby2_keywords (>= 0.0.5)
|
|
36
|
-
mysql2 (0.5.5)
|
|
37
|
-
rake (13.0.6)
|
|
38
|
-
retriable (3.1.2)
|
|
39
|
-
ruby2_keywords (0.0.5)
|
|
40
|
-
simplecov (0.22.0)
|
|
41
|
-
docile (~> 1.1)
|
|
42
|
-
simplecov-html (~> 0.11)
|
|
43
|
-
simplecov_json_formatter (~> 0.1)
|
|
44
|
-
simplecov-html (0.12.3)
|
|
45
|
-
simplecov_json_formatter (0.1.4)
|
|
46
|
-
thor (1.2.2)
|
|
47
|
-
toxiproxy (2.0.2)
|
|
48
|
-
trilogy (2.6.0)
|
|
49
|
-
tzinfo (2.0.6)
|
|
50
|
-
concurrent-ruby (~> 1.0)
|
|
51
|
-
|
|
52
|
-
PLATFORMS
|
|
53
|
-
arm64-darwin-21
|
|
54
|
-
arm64-darwin-22
|
|
55
|
-
x86_64-darwin-20
|
|
56
|
-
x86_64-linux
|
|
57
|
-
|
|
58
|
-
DEPENDENCIES
|
|
59
|
-
activerecord (= 7.0.8)
|
|
60
|
-
activerecord-trilogy-adapter
|
|
61
|
-
after_do
|
|
62
|
-
appraisal
|
|
63
|
-
byebug
|
|
64
|
-
lhm-shopify!
|
|
65
|
-
minitest
|
|
66
|
-
mocha
|
|
67
|
-
mysql2
|
|
68
|
-
rake
|
|
69
|
-
simplecov
|
|
70
|
-
toxiproxy
|
|
71
|
-
trilogy
|
|
72
|
-
|
|
73
|
-
BUNDLED WITH
|
|
74
|
-
2.2.22
|