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 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: