cassandra-schema 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 56d06dbe1704f5dde353807ef36e71c49b6cd431
4
- data.tar.gz: 3010c1e9b8556c0c8556ab95007299c4d925f0cc
3
+ metadata.gz: b8952ab5b3596d5370d883b95b1fd0729db5aa1d
4
+ data.tar.gz: 89eb6476ec2461fe77b5f531c1c42a7bdbb27e7d
5
5
  SHA512:
6
- metadata.gz: a769484a9b5ea4d7edd06a783e1e2f29176ff0fbe4ba2ade021ac6c3829562f2591282ef3f55518d681c5afd21b08fcfaf2e3cf654da6fb76d5e805b569f2be4
7
- data.tar.gz: dca8a92e44664c35676cb269d373306f11f63e563802d3f3bc802f5d5fd4d802ca8404c5a83fdf0c46d73bf91f706f3aa9f5a23d85ab911922ba45ea8de30f61
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`.
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = "cassandra-schema"
5
- s.version = "0.1.0"
5
+ s.version = "0.2.0"
6
6
  s.summary = "Cassandra schema migrations"
7
7
  s.license = "MIT"
8
8
  s.description = "Simple reversible schema migrations for Cassandra."
@@ -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
- def initialize(connection:, migrations:, logger: Logger.new(STDOUT))
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
- result = @connection.execute <<~CQL
52
- CREATE TABLE IF NOT EXISTS schema_information (
53
- name VARCHAR,
54
- value VARCHAR,
55
- PRIMARY KEY (name)
56
- );
57
- CQL
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 <<~CQL
62
- SELECT value FROM schema_information WHERE name = 'version'
63
- CQL
64
-
65
- result.rows.any? && result.rows.first["value"].to_i
66
- end
88
+ result = @connection.execute(
89
+ <<~CQL,
90
+ SELECT value FROM schema_information WHERE name = 'version'
91
+ CQL
92
+ consistency: :quorum
93
+ )
67
94
 
68
- def init_versioning
69
- @connection.execute <<~CQL
70
- INSERT INTO schema_information(name, value) VALUES('version', '0')
71
- CQL
95
+ unless result.rows.any?
96
+ @logger.info "Can't load current schema version."
97
+ fail
98
+ end
72
99
 
73
- 0
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}"
@@ -1,3 +1,3 @@
1
1
  module CassandraSchema
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -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
- assert_equal 0, migrator.current_version
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
- @migrator.migrate(1)
156
- assert_equal 1, @migrator.current_version
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.1.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-09-15 00:00:00.000000000 Z
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: