lhm 1.0.0.rc.1
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.
- data/.gitignore +5 -0
- data/.travis.yml +10 -0
- data/CHANGELOG.md +32 -0
- data/Gemfile +3 -0
- data/LICENSE +27 -0
- data/README.md +98 -0
- data/Rakefile +16 -0
- data/TODO +11 -0
- data/lhm.gemspec +29 -0
- data/lib/lhm.rb +20 -0
- data/lib/lhm/chunker.rb +73 -0
- data/lib/lhm/command.rb +70 -0
- data/lib/lhm/entangler.rb +105 -0
- data/lib/lhm/intersection.rb +42 -0
- data/lib/lhm/invoker.rb +37 -0
- data/lib/lhm/locked_switcher.rb +78 -0
- data/lib/lhm/migration.rb +34 -0
- data/lib/lhm/migrator.rb +125 -0
- data/lib/lhm/table.rb +87 -0
- data/spec/bootstrap.rb +16 -0
- data/spec/fixtures/destination.ddl +7 -0
- data/spec/fixtures/origin.ddl +7 -0
- data/spec/fixtures/users.ddl +11 -0
- data/spec/integration/chunker_spec.rb +31 -0
- data/spec/integration/entangler_spec.rb +60 -0
- data/spec/integration/integration_helper.rb +74 -0
- data/spec/integration/lhm_spec.rb +118 -0
- data/spec/integration/locked_switcher_spec.rb +41 -0
- data/spec/unit/chunker_spec.rb +79 -0
- data/spec/unit/entangler_spec.rb +79 -0
- data/spec/unit/intersection_spec.rb +42 -0
- data/spec/unit/locked_switcher_spec.rb +54 -0
- data/spec/unit/migration_spec.rb +26 -0
- data/spec/unit/migrator_spec.rb +81 -0
- data/spec/unit/table_spec.rb +88 -0
- data/spec/unit/unit_helper.rb +17 -0
- metadata +165 -0
@@ -0,0 +1,42 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2011, SoundCloud Ltd., Rany Keddo, Tobias Bielohlawek, Tobias
|
3
|
+
# Schmidt
|
4
|
+
#
|
5
|
+
|
6
|
+
require File.expand_path(File.dirname(__FILE__)) + '/unit_helper'
|
7
|
+
|
8
|
+
require 'lhm/table'
|
9
|
+
require 'lhm/migrator'
|
10
|
+
|
11
|
+
describe Lhm::Intersection do
|
12
|
+
include UnitHelper
|
13
|
+
|
14
|
+
it "should not have dropped changes" do
|
15
|
+
origin = Lhm::Table.new("origin")
|
16
|
+
origin.columns["dropped"] = varchar
|
17
|
+
origin.columns["retained"] = varchar
|
18
|
+
|
19
|
+
destination = Lhm::Table.new("destination")
|
20
|
+
destination.columns["retained"] = varchar
|
21
|
+
|
22
|
+
intersection = Lhm::Intersection.new(origin, destination)
|
23
|
+
intersection.common.include?("dropped").must_equal(false)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should have unchanged columns" do
|
27
|
+
origin = Lhm::Table.new("origin")
|
28
|
+
origin.columns["dropped"] = varchar
|
29
|
+
origin.columns["retained"] = varchar
|
30
|
+
|
31
|
+
destination = Lhm::Table.new("destination")
|
32
|
+
destination.columns["retained"] = varchar
|
33
|
+
|
34
|
+
intersection = Lhm::Intersection.new(origin, destination)
|
35
|
+
intersection.common.must_equal(["retained"])
|
36
|
+
end
|
37
|
+
|
38
|
+
def varchar
|
39
|
+
{ :metadata => "VARCHAR(255)"}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
@@ -0,0 +1,54 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2011, SoundCloud Ltd., Rany Keddo, Tobias Bielohlawek, Tobias
|
3
|
+
# Schmidt
|
4
|
+
#
|
5
|
+
|
6
|
+
require File.expand_path(File.dirname(__FILE__)) + '/unit_helper'
|
7
|
+
|
8
|
+
require 'lhm/table'
|
9
|
+
require 'lhm/migration'
|
10
|
+
require 'lhm/locked_switcher'
|
11
|
+
|
12
|
+
describe Lhm::LockedSwitcher do
|
13
|
+
include UnitHelper
|
14
|
+
|
15
|
+
before(:each) do
|
16
|
+
@start = Time.now
|
17
|
+
@origin = Lhm::Table.new("origin")
|
18
|
+
@destination = Lhm::Table.new("destination")
|
19
|
+
@migration = Lhm::Migration.new(@origin, @destination, @start)
|
20
|
+
@switcher = Lhm::LockedSwitcher.new(@migration)
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "uncommitted" do
|
24
|
+
it "should disable autocommit first" do
|
25
|
+
@switcher.
|
26
|
+
statements[0..1].
|
27
|
+
must_equal([
|
28
|
+
"set @lhm_auto_commit = @@session.autocommit",
|
29
|
+
"set session autocommit = 0"
|
30
|
+
])
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should reapply original autocommit settings at the end" do
|
34
|
+
@switcher.
|
35
|
+
statements[-1].
|
36
|
+
must_equal("set session autocommit = @lhm_auto_commit")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "switch" do
|
41
|
+
it "should lock origin and destination table, switch, commit and unlock" do
|
42
|
+
@switcher.
|
43
|
+
switch.
|
44
|
+
must_equal([
|
45
|
+
"lock table `origin` write, `destination` write",
|
46
|
+
"alter table `origin` rename `#{ @migration.archive_name }`",
|
47
|
+
"alter table `destination` rename `origin`",
|
48
|
+
"commit",
|
49
|
+
"unlock tables"
|
50
|
+
])
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2011, SoundCloud Ltd., Rany Keddo, Tobias Bielohlawek, Tobias
|
3
|
+
# Schmidt
|
4
|
+
#
|
5
|
+
|
6
|
+
require File.expand_path(File.dirname(__FILE__)) + '/unit_helper'
|
7
|
+
|
8
|
+
require 'lhm/table'
|
9
|
+
require 'lhm/migration'
|
10
|
+
|
11
|
+
describe Lhm::Migration do
|
12
|
+
include UnitHelper
|
13
|
+
|
14
|
+
before(:each) do
|
15
|
+
@start = Time.now
|
16
|
+
@origin = Lhm::Table.new("origin")
|
17
|
+
@destination = Lhm::Table.new("destination")
|
18
|
+
@migration = Lhm::Migration.new(@origin, @destination, @start)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should name archive" do
|
22
|
+
stamp = "%Y_%m_%d_%H_%M_%S_#{ "%03d" % (@start.usec / 1000) }"
|
23
|
+
@migration.archive_name.must_equal "lhma_#{ @start.strftime(stamp) }_origin"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
@@ -0,0 +1,81 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2011, SoundCloud Ltd., Rany Keddo, Tobias Bielohlawek, Tobias
|
3
|
+
# Schmidt
|
4
|
+
#
|
5
|
+
|
6
|
+
require File.expand_path(File.dirname(__FILE__)) + '/unit_helper'
|
7
|
+
|
8
|
+
require 'lhm/table'
|
9
|
+
require 'lhm/migrator'
|
10
|
+
|
11
|
+
describe Lhm::Migrator do
|
12
|
+
include UnitHelper
|
13
|
+
|
14
|
+
before(:each) do
|
15
|
+
@table = Lhm::Table.new("alt")
|
16
|
+
@creator = Lhm::Migrator.new(@table)
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "index changes" do
|
20
|
+
it "should add an index" do
|
21
|
+
@creator.add_index(["a", "b"])
|
22
|
+
|
23
|
+
@creator.statements.must_equal([
|
24
|
+
"create index `index_alt_on_a_and_b` on lhmn_alt(a, b)"
|
25
|
+
])
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should remove an index" do
|
29
|
+
@creator.remove_index(["b", "a"])
|
30
|
+
|
31
|
+
@creator.statements.must_equal([
|
32
|
+
"drop index `index_alt_on_b_and_a` on `lhmn_alt`"
|
33
|
+
])
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "column changes" do
|
38
|
+
it "should add a column" do
|
39
|
+
@creator.add_column("logins", "INT(12)")
|
40
|
+
|
41
|
+
@creator.statements.must_equal([
|
42
|
+
"alter table `lhmn_alt` add column `logins` INT(12)"
|
43
|
+
])
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should remove a column" do
|
47
|
+
@creator.remove_column("logins")
|
48
|
+
|
49
|
+
@creator.statements.must_equal([
|
50
|
+
"alter table `lhmn_alt` drop `logins`"
|
51
|
+
])
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "direct changes" do
|
56
|
+
it "should accept a ddl statement" do
|
57
|
+
ddl = @creator.ddl("alter table `%s` add column `f` tinyint(1)" % @creator.name)
|
58
|
+
|
59
|
+
@creator.statements.must_equal([
|
60
|
+
"alter table `lhmn_alt` add column `f` tinyint(1)"
|
61
|
+
])
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "multiple changes" do
|
66
|
+
it "should add two columns" do
|
67
|
+
@creator.add_column("first", "VARCHAR(64)")
|
68
|
+
@creator.add_column("last", "VARCHAR(64)")
|
69
|
+
@creator.statements.length.must_equal(2)
|
70
|
+
|
71
|
+
@creator.
|
72
|
+
statements[0].
|
73
|
+
must_equal("alter table `lhmn_alt` add column `first` VARCHAR(64)")
|
74
|
+
|
75
|
+
@creator.
|
76
|
+
statements[1].
|
77
|
+
must_equal("alter table `lhmn_alt` add column `last` VARCHAR(64)")
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
@@ -0,0 +1,88 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2011, SoundCloud Ltd., Rany Keddo, Tobias Bielohlawek, Tobias
|
3
|
+
# Schmidt
|
4
|
+
#
|
5
|
+
|
6
|
+
require File.expand_path(File.dirname(__FILE__)) + '/unit_helper'
|
7
|
+
|
8
|
+
require 'lhm/table'
|
9
|
+
|
10
|
+
describe Lhm::Table do
|
11
|
+
include UnitHelper
|
12
|
+
|
13
|
+
describe "names" do
|
14
|
+
before(:each) do
|
15
|
+
@table = Lhm::Table::Parser.new(fixture("users.ddl")).parse
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should name destination" do
|
19
|
+
@table.destination_name.must_equal "lhmn_users"
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should name index with a single column" do
|
23
|
+
@table.
|
24
|
+
idx_name(["name"]).
|
25
|
+
must_equal("index_users_on_name")
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should name index with a multiple columns" do
|
29
|
+
@table.
|
30
|
+
idx_name(["name", "firstname"]).
|
31
|
+
must_equal("index_users_on_name_and_firstname")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "constraints" do
|
36
|
+
it "should be satisfied with a single column primary key called id" do
|
37
|
+
@table = Lhm::Table.new("table", "id")
|
38
|
+
@table.satisfies_primary_key?.must_equal true
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should not be satisfied with a primary key unless called id" do
|
42
|
+
@table = Lhm::Table.new("table", "uuid")
|
43
|
+
@table.satisfies_primary_key?.must_equal false
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should not be satisfied with multicolumn primary key" do
|
47
|
+
@table = Lhm::Table.new("table", ["id", "secondary"])
|
48
|
+
@table.satisfies_primary_key?.must_equal false
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe Lhm::Table::Parser do
|
53
|
+
describe "create table parsing" do
|
54
|
+
before(:each) do
|
55
|
+
@table = Lhm::Table::Parser.new(fixture("users.ddl")).parse
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should parse table name in show create table" do
|
59
|
+
@table.name.must_equal("users")
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should parse primary key" do
|
63
|
+
@table.pk.must_equal("id")
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should parse column type in show create table" do
|
67
|
+
@table.columns["username"][:type].must_equal("varchar(255)")
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should parse column metadata" do
|
71
|
+
@table.columns["username"][:metadata].must_equal("DEFAULT NULL")
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should parse indices in show create table" do
|
75
|
+
@table.
|
76
|
+
indices["index_users_on_username_and_created_at"][:metadata].
|
77
|
+
must_equal("(`username`,`created_at`)")
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should parse indices in show create table" do
|
81
|
+
@table.
|
82
|
+
indices["index_users_on_reference"][:metadata].
|
83
|
+
must_equal("(`reference`)")
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2011, SoundCloud Ltd., Rany Keddo, Tobias Bielohlawek, Tobias
|
3
|
+
# Schmidt
|
4
|
+
#
|
5
|
+
|
6
|
+
require File.expand_path(File.dirname(__FILE__)) + "/../bootstrap"
|
7
|
+
|
8
|
+
module UnitHelper
|
9
|
+
def fixture(name)
|
10
|
+
File.read $fixtures.join(name)
|
11
|
+
end
|
12
|
+
|
13
|
+
def strip(sql)
|
14
|
+
sql.strip.gsub(/\n */, "\n")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
metadata
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lhm
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: true
|
5
|
+
segments:
|
6
|
+
- 1
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- rc
|
10
|
+
- 1
|
11
|
+
version: 1.0.0.rc.1
|
12
|
+
platform: ruby
|
13
|
+
authors:
|
14
|
+
- SoundCloud
|
15
|
+
- Rany Keddo
|
16
|
+
- Tobias Bielohlawek
|
17
|
+
- Tobias Schmidt
|
18
|
+
autorequire:
|
19
|
+
bindir: bin
|
20
|
+
cert_chain: []
|
21
|
+
|
22
|
+
date: 2012-01-16 00:00:00 +01:00
|
23
|
+
default_executable:
|
24
|
+
dependencies:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: mysql
|
27
|
+
prerelease: false
|
28
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
29
|
+
none: false
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
segments:
|
34
|
+
- 2
|
35
|
+
- 8
|
36
|
+
- 1
|
37
|
+
version: 2.8.1
|
38
|
+
type: :development
|
39
|
+
version_requirements: *id001
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: rspec
|
42
|
+
prerelease: false
|
43
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
44
|
+
none: false
|
45
|
+
requirements:
|
46
|
+
- - "="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
segments:
|
49
|
+
- 1
|
50
|
+
- 3
|
51
|
+
- 1
|
52
|
+
version: 1.3.1
|
53
|
+
type: :development
|
54
|
+
version_requirements: *id002
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
prerelease: false
|
58
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
segments:
|
64
|
+
- 0
|
65
|
+
version: "0"
|
66
|
+
type: :development
|
67
|
+
version_requirements: *id003
|
68
|
+
description: Migrate large tables without downtime by copying to a temporary table in chunks. The old table is not dropped. Instead, it is moved to timestamp_table_name for verification.
|
69
|
+
email: rany@soundcloud.com, tobi@soundcloud.com, ts@soundcloud.com
|
70
|
+
executables: []
|
71
|
+
|
72
|
+
extensions: []
|
73
|
+
|
74
|
+
extra_rdoc_files: []
|
75
|
+
|
76
|
+
files:
|
77
|
+
- .gitignore
|
78
|
+
- .travis.yml
|
79
|
+
- CHANGELOG.md
|
80
|
+
- Gemfile
|
81
|
+
- Gemfile.lock
|
82
|
+
- LICENSE
|
83
|
+
- README.md
|
84
|
+
- Rakefile
|
85
|
+
- TODO
|
86
|
+
- lhm.gemspec
|
87
|
+
- lib/lhm.rb
|
88
|
+
- lib/lhm/chunker.rb
|
89
|
+
- lib/lhm/command.rb
|
90
|
+
- lib/lhm/entangler.rb
|
91
|
+
- lib/lhm/intersection.rb
|
92
|
+
- lib/lhm/invoker.rb
|
93
|
+
- lib/lhm/locked_switcher.rb
|
94
|
+
- lib/lhm/migration.rb
|
95
|
+
- lib/lhm/migrator.rb
|
96
|
+
- lib/lhm/table.rb
|
97
|
+
- spec/bootstrap.rb
|
98
|
+
- spec/fixtures/destination.ddl
|
99
|
+
- spec/fixtures/origin.ddl
|
100
|
+
- spec/fixtures/users.ddl
|
101
|
+
- spec/integration/chunker_spec.rb
|
102
|
+
- spec/integration/entangler_spec.rb
|
103
|
+
- spec/integration/integration_helper.rb
|
104
|
+
- spec/integration/lhm_spec.rb
|
105
|
+
- spec/integration/locked_switcher_spec.rb
|
106
|
+
- spec/unit/chunker_spec.rb
|
107
|
+
- spec/unit/entangler_spec.rb
|
108
|
+
- spec/unit/intersection_spec.rb
|
109
|
+
- spec/unit/locked_switcher_spec.rb
|
110
|
+
- spec/unit/migration_spec.rb
|
111
|
+
- spec/unit/migrator_spec.rb
|
112
|
+
- spec/unit/table_spec.rb
|
113
|
+
- spec/unit/unit_helper.rb
|
114
|
+
has_rdoc: true
|
115
|
+
homepage: http://github.com/soundcloud/large-hadron-migrator
|
116
|
+
licenses: []
|
117
|
+
|
118
|
+
post_install_message:
|
119
|
+
rdoc_options: []
|
120
|
+
|
121
|
+
require_paths:
|
122
|
+
- lib
|
123
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
124
|
+
none: false
|
125
|
+
requirements:
|
126
|
+
- - ">="
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
segments:
|
129
|
+
- 0
|
130
|
+
version: "0"
|
131
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
132
|
+
none: false
|
133
|
+
requirements:
|
134
|
+
- - ">"
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
segments:
|
137
|
+
- 1
|
138
|
+
- 3
|
139
|
+
- 1
|
140
|
+
version: 1.3.1
|
141
|
+
requirements: []
|
142
|
+
|
143
|
+
rubyforge_project:
|
144
|
+
rubygems_version: 1.3.7
|
145
|
+
signing_key:
|
146
|
+
specification_version: 3
|
147
|
+
summary: online schema changer for mysql
|
148
|
+
test_files:
|
149
|
+
- spec/bootstrap.rb
|
150
|
+
- spec/fixtures/destination.ddl
|
151
|
+
- spec/fixtures/origin.ddl
|
152
|
+
- spec/fixtures/users.ddl
|
153
|
+
- spec/integration/chunker_spec.rb
|
154
|
+
- spec/integration/entangler_spec.rb
|
155
|
+
- spec/integration/integration_helper.rb
|
156
|
+
- spec/integration/lhm_spec.rb
|
157
|
+
- spec/integration/locked_switcher_spec.rb
|
158
|
+
- spec/unit/chunker_spec.rb
|
159
|
+
- spec/unit/entangler_spec.rb
|
160
|
+
- spec/unit/intersection_spec.rb
|
161
|
+
- spec/unit/locked_switcher_spec.rb
|
162
|
+
- spec/unit/migration_spec.rb
|
163
|
+
- spec/unit/migrator_spec.rb
|
164
|
+
- spec/unit/table_spec.rb
|
165
|
+
- spec/unit/unit_helper.rb
|