cassandra-schema 0.1.0 → 0.2.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/README.md +7 -0
- data/cassandra-schema.gemspec +1 -1
- data/lib/cassandra-schema/migrator.rb +86 -23
- data/lib/cassandra-schema/version.rb +1 -1
- data/test/migrator_test.rb +57 -3
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b8952ab5b3596d5370d883b95b1fd0729db5aa1d
|
4
|
+
data.tar.gz: 89eb6476ec2461fe77b5f531c1c42a7bdbb27e7d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9e7e3df43fa96784a8486aa3b4f1215090d718b92e77590c9b12e5a88f9abfe92ffd7cf0656917e6826436922ccef564e3791908149bd98522fb419870ef89d6
|
7
|
+
data.tar.gz: 220f759eff1b2c967b559d6850018087e38a6e58bbf13374229dcdbe12de2ea7dfc73f3e11556c5beeefc47c056597bf0c1fd8efb444fefc3ab889a989e790f6
|
data/README.md
CHANGED
@@ -2,6 +2,13 @@
|
|
2
2
|
|
3
3
|
Simple reversible schema migrations for cassandra.
|
4
4
|
|
5
|
+
## Changelog
|
6
|
+
|
7
|
+
### Version `0.2.0`
|
8
|
+
|
9
|
+
- Refactor `schema_information` queries to use LWT and `:quorum` consistency level
|
10
|
+
- Implement a simple locking system to prevent concurrent migrations
|
11
|
+
|
5
12
|
## Usage
|
6
13
|
|
7
14
|
CassandraSchema uses a DSL via the `CassandraSchema.migration(version)` method. A migration must have an `up` block with the changes you want to apply to the schema, and a `down` block reversing the change made by `up`.
|
data/cassandra-schema.gemspec
CHANGED
@@ -2,19 +2,30 @@ require_relative "migration"
|
|
2
2
|
|
3
3
|
module CassandraSchema
|
4
4
|
class Migrator
|
5
|
-
attr_reader :connection, :current_version
|
5
|
+
attr_reader :connection, :current_version, :options
|
6
6
|
|
7
|
-
|
7
|
+
DEFAULT_OPTIONS = {
|
8
|
+
lock: true,
|
9
|
+
lock_timeout: 30,
|
10
|
+
}
|
11
|
+
|
12
|
+
def initialize(connection:, migrations:, logger: Logger.new(STDOUT), options: {})
|
8
13
|
@connection = connection
|
9
14
|
@logger = logger
|
10
15
|
@migrations = migrations
|
16
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
11
17
|
|
12
18
|
generate_migrator_schema!
|
13
|
-
|
14
|
-
@current_version = get_current_version || init_versioning
|
15
19
|
end
|
16
20
|
|
17
21
|
def migrate(target = nil)
|
22
|
+
if @options.fetch(:lock) && !lock_schema
|
23
|
+
@logger.info "Can't run migrations. Schema is locked."
|
24
|
+
return
|
25
|
+
end
|
26
|
+
|
27
|
+
@current_version = get_current_version
|
28
|
+
|
18
29
|
target ||= @migrations.keys.max || 0
|
19
30
|
|
20
31
|
@logger.info "Running migrations..."
|
@@ -29,12 +40,14 @@ module CassandraSchema
|
|
29
40
|
# excludes current version's up
|
30
41
|
(current_version + 1).upto(target) do |next_version|
|
31
42
|
migrate_to(next_version, :up)
|
43
|
+
renew_lock if @options.fetch(:lock)
|
32
44
|
end
|
33
45
|
else
|
34
46
|
# includes current version's :down
|
35
47
|
# excludes target version's :down
|
36
48
|
current_version.downto(target + 1) do |version|
|
37
49
|
migrate_to(version, :down)
|
50
|
+
renew_lock if @options.fetch(:lock)
|
38
51
|
end
|
39
52
|
end
|
40
53
|
|
@@ -42,35 +55,49 @@ module CassandraSchema
|
|
42
55
|
@logger.info "Done!"
|
43
56
|
rescue => ex
|
44
57
|
@logger.info "Failed migrating all files. Current schema version: #{@current_version}"
|
58
|
+
ensure
|
59
|
+
unlock_schema if @options.fetch(:lock)
|
45
60
|
end
|
46
61
|
end
|
47
62
|
|
48
63
|
private
|
49
64
|
|
50
65
|
def generate_migrator_schema!
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
66
|
+
@connection.execute(
|
67
|
+
<<~CQL,
|
68
|
+
CREATE TABLE IF NOT EXISTS schema_information (
|
69
|
+
name VARCHAR,
|
70
|
+
value VARCHAR,
|
71
|
+
PRIMARY KEY (name)
|
72
|
+
);
|
73
|
+
CQL
|
74
|
+
consistency: :quorum
|
75
|
+
)
|
76
|
+
|
77
|
+
@connection.execute(
|
78
|
+
<<~CQL,
|
79
|
+
INSERT INTO schema_information(name, value)
|
80
|
+
VALUES('version', '0')
|
81
|
+
IF NOT EXISTS
|
82
|
+
CQL
|
83
|
+
consistency: :quorum
|
84
|
+
)
|
58
85
|
end
|
59
86
|
|
60
87
|
def get_current_version
|
61
|
-
result = @connection.execute
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
88
|
+
result = @connection.execute(
|
89
|
+
<<~CQL,
|
90
|
+
SELECT value FROM schema_information WHERE name = 'version'
|
91
|
+
CQL
|
92
|
+
consistency: :quorum
|
93
|
+
)
|
67
94
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
95
|
+
unless result.rows.any?
|
96
|
+
@logger.info "Can't load current schema version."
|
97
|
+
fail
|
98
|
+
end
|
72
99
|
|
73
|
-
|
100
|
+
result.rows.first["value"].to_i
|
74
101
|
end
|
75
102
|
|
76
103
|
def update_version(target)
|
@@ -78,12 +105,48 @@ module CassandraSchema
|
|
78
105
|
<<~CQL,
|
79
106
|
UPDATE schema_information SET value = ? WHERE name = 'version'
|
80
107
|
CQL
|
81
|
-
arguments: [target.to_s]
|
108
|
+
arguments: [target.to_s],
|
109
|
+
consistency: :quorum
|
82
110
|
)
|
83
111
|
|
84
112
|
@current_version = target
|
85
113
|
end
|
86
114
|
|
115
|
+
def lock_schema
|
116
|
+
result = @connection.execute(
|
117
|
+
<<~CQL,
|
118
|
+
INSERT INTO schema_information(name, value)
|
119
|
+
VALUES('lock', '1')
|
120
|
+
IF NOT EXISTS
|
121
|
+
USING TTL #{@options.fetch(:lock_timeout)}
|
122
|
+
CQL
|
123
|
+
consistency: :quorum
|
124
|
+
)
|
125
|
+
|
126
|
+
result.rows.first.fetch("[applied]")
|
127
|
+
end
|
128
|
+
|
129
|
+
def renew_lock
|
130
|
+
@connection.execute(
|
131
|
+
<<~CQL,
|
132
|
+
UPDATE schema_information
|
133
|
+
USING TTL #{@options.fetch(:lock_timeout)}
|
134
|
+
SET value = '1'
|
135
|
+
WHERE name = 'lock'
|
136
|
+
CQL
|
137
|
+
consistency: :quorum
|
138
|
+
)
|
139
|
+
end
|
140
|
+
|
141
|
+
def unlock_schema
|
142
|
+
@connection.execute(
|
143
|
+
<<~CQL,
|
144
|
+
DELETE FROM schema_information WHERE name = 'lock' IF EXISTS;
|
145
|
+
CQL
|
146
|
+
consistency: :quorum
|
147
|
+
)
|
148
|
+
end
|
149
|
+
|
87
150
|
def migrate_to(target, direction)
|
88
151
|
new_version = direction == :up ? target : target - 1
|
89
152
|
@logger.info "Migrating to version #{new_version}"
|
data/test/migrator_test.rb
CHANGED
@@ -74,8 +74,24 @@ describe "CassandraSchema::Migrator" do
|
|
74
74
|
end
|
75
75
|
|
76
76
|
down do
|
77
|
+
execute "DROP MATERIALIZED VIEW table_by_description"
|
78
|
+
|
77
79
|
execute "ALTER TABLE table_by_name DROP alt_email"
|
78
80
|
execute "ALTER TABLE table_by_name DROP email"
|
81
|
+
|
82
|
+
execute <<~CQL
|
83
|
+
CREATE MATERIALIZED VIEW table_by_description AS
|
84
|
+
SELECT
|
85
|
+
id,
|
86
|
+
name,
|
87
|
+
description
|
88
|
+
FROM table_by_name
|
89
|
+
WHERE id IS NOT NULL
|
90
|
+
AND name IS NOT NULL
|
91
|
+
AND description IS NOT NULL
|
92
|
+
PRIMARY KEY (id, description, name)
|
93
|
+
WITH CLUSTERING ORDER BY (description ASC, name ASC)
|
94
|
+
CQL
|
79
95
|
end
|
80
96
|
end
|
81
97
|
|
@@ -89,7 +105,9 @@ describe "CassandraSchema::Migrator" do
|
|
89
105
|
logger: @fake_logger,
|
90
106
|
)
|
91
107
|
|
92
|
-
|
108
|
+
result = CONN.execute "SELECT value FROM schema_information WHERE name = 'version'"
|
109
|
+
|
110
|
+
assert result.rows.first.fetch("value")
|
93
111
|
end
|
94
112
|
|
95
113
|
describe "migrating up" do
|
@@ -138,6 +156,35 @@ describe "CassandraSchema::Migrator" do
|
|
138
156
|
assert_equal "Failed migrating all files. Current schema version: 2", @fake_logger.stdout.pop
|
139
157
|
assert_equal "Missing migration with version 3", @fake_logger.stdout.pop
|
140
158
|
end
|
159
|
+
|
160
|
+
it "fails if another migration is running" do
|
161
|
+
migrator_a = CassandraSchema::Migrator.new(
|
162
|
+
connection: CONN,
|
163
|
+
migrations: CassandraSchema.migrations,
|
164
|
+
logger: @fake_logger,
|
165
|
+
)
|
166
|
+
|
167
|
+
thr = Thread.new do
|
168
|
+
migrator_a.migrate
|
169
|
+
end
|
170
|
+
|
171
|
+
logger_b = FakeLogger.new
|
172
|
+
migrator_b = CassandraSchema::Migrator.new(
|
173
|
+
connection: CONN,
|
174
|
+
migrations: CassandraSchema.migrations,
|
175
|
+
logger: logger_b,
|
176
|
+
)
|
177
|
+
|
178
|
+
migrator_b.migrate
|
179
|
+
|
180
|
+
assert_equal "Can't run migrations. Schema is locked.", logger_b.stdout.pop
|
181
|
+
|
182
|
+
thr.join
|
183
|
+
|
184
|
+
migrator_b.migrate
|
185
|
+
|
186
|
+
assert_equal "Nothing to migrate.", logger_b.stdout.pop
|
187
|
+
end
|
141
188
|
end
|
142
189
|
|
143
190
|
describe "migrating down" do
|
@@ -152,8 +199,15 @@ describe "CassandraSchema::Migrator" do
|
|
152
199
|
end
|
153
200
|
|
154
201
|
it "migrates to target version" do
|
155
|
-
|
156
|
-
|
202
|
+
logger = FakeLogger.new
|
203
|
+
migrator = CassandraSchema::Migrator.new(
|
204
|
+
connection: CONN,
|
205
|
+
migrations: CassandraSchema.migrations,
|
206
|
+
logger: logger,
|
207
|
+
)
|
208
|
+
migrator.migrate(1)
|
209
|
+
|
210
|
+
assert_equal 1, migrator.current_version
|
157
211
|
end
|
158
212
|
end
|
159
213
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cassandra-schema
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lautaro Orazi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-12-28 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Simple reversible schema migrations for Cassandra.
|
14
14
|
email:
|