cassandra-schema 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 56d06dbe1704f5dde353807ef36e71c49b6cd431
4
+ data.tar.gz: 3010c1e9b8556c0c8556ab95007299c4d925f0cc
5
+ SHA512:
6
+ metadata.gz: a769484a9b5ea4d7edd06a783e1e2f29176ff0fbe4ba2ade021ac6c3829562f2591282ef3f55518d681c5afd21b08fcfaf2e3cf654da6fb76d5e805b569f2be4
7
+ data.tar.gz: dca8a92e44664c35676cb269d373306f11f63e563802d3f3bc802f5d5fd4d802ca8404c5a83fdf0c46d73bf91f706f3aa9f5a23d85ab911922ba45ea8de30f61
@@ -0,0 +1,2 @@
1
+ cassandra-driver -v 3.2.0
2
+ lz4-ruby -v 0.3.3
@@ -0,0 +1,3 @@
1
+ tags
2
+ .ruby-version
3
+ *.gem
@@ -0,0 +1,19 @@
1
+ FROM ruby:2.4.1-alpine
2
+
3
+ # Tell pry to use `more` as the pager because the installed version of `less`
4
+ # does not support passing the `-R` which is used by pry.
5
+ ENV PAGER="more"
6
+
7
+ # Install runtime dependencies
8
+ RUN apk add --update libcurl
9
+
10
+ # Install gem dependency specifications.
11
+ COPY .gems-test /tmp/
12
+
13
+ # Install gem build dependencies, install gems, purge gem build dependencies.
14
+ RUN apk add --update -t gem-build-deps libffi-dev make gcc g++ musl-dev && \
15
+ gem install dep:1.1.0 && \
16
+ cd /tmp && \
17
+ dep -f .gems install && dep -f .gems && rm .gems && \
18
+ dep -f .gems-test install && dep -f .gems-test && rm .gems-test && \
19
+ apk del --purge gem-build-deps
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2017 Lautaro Orazi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,124 @@
1
+ # Cassandra Schema [![CircleCI](https://circleci.com/gh/tarolandia/cassandra-schema/tree/master.svg?style=svg)](https://circleci.com/gh/tarolandia/cassandra-schema/tree/master)
2
+
3
+ Simple reversible schema migrations for cassandra.
4
+
5
+ ## Usage
6
+
7
+ 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`.
8
+
9
+ Use `execute` inside `up` and `down` to run the queries that will modify the schema.
10
+
11
+
12
+ Here is an example of a migration file:
13
+
14
+ ```cql
15
+ require "cassandra-schema"
16
+
17
+ CassandraSchema.migration(1) do
18
+ up do
19
+ execute <<~CQL
20
+ CREATE TABLE table_by_name (
21
+ id uuid,
22
+ name text,
23
+ description text,
24
+ PRIMARY KEY (id, name)
25
+ ) WITH CLUSTERING ORDER BY (name ASC)
26
+ CQL
27
+
28
+ execute <<~CQL
29
+ CREATE MATERIALIZED VIEW table_by_description AS
30
+ SELECT
31
+ id,
32
+ name,
33
+ description
34
+ FROM table_by_name
35
+ WHERE id IS NOT NULL
36
+ AND name IS NOT NULL
37
+ AND description IS NOT NULL
38
+ PRIMARY KEY (id, description, name)
39
+ WITH CLUSTERING ORDER BY (description ASC, name ASC)
40
+ CQL
41
+ end
42
+
43
+ down do
44
+ execute "DROP MATERIALIZED VIEW table_by_description"
45
+ execute "DROP TABLE table_by_name"
46
+ end
47
+ end
48
+ ```
49
+
50
+ ## Running Migrations
51
+
52
+ Once you defined your migrations, you can use `CassandraSchema::Migrator` to run them.
53
+
54
+ ```ruby
55
+ require "cassandra-schema/migrator"
56
+
57
+ migrator = CassandraSchema::Migrator.new(
58
+ connection: CONN, # any connection object implementing `execute` method
59
+ migrations: CassandraSchema.migrations, # list of migrations
60
+ logger: Logger.new, # any logger object implementing `info` and `error` methods
61
+ )
62
+ ```
63
+
64
+ Migrate to lastest version:
65
+
66
+ ```ruby
67
+ migrator.migrate
68
+ ```
69
+
70
+ Migrate to certain version:
71
+
72
+ ```ruby
73
+ migrator.migrate(2)
74
+ ```
75
+
76
+ CassandraSchema tracks which migrations you have already run.
77
+
78
+ ## Installation
79
+
80
+ You can install it using rubygems.
81
+
82
+ ```
83
+ gem install cassandra-schema
84
+ ```
85
+
86
+ ## How to collaborate
87
+
88
+ If you find a bug or want to collaborate with the code, you can:
89
+
90
+ * Report issues trhough the issue tracker
91
+ * Fork the repository into your own account and submit a Pull Request
92
+
93
+ ## Credits
94
+
95
+ These people have donated time to reviewing and improving this gem:
96
+
97
+ * [Ary Borenszweig](https://github.com/asterite)
98
+ * [Joe Eli McIlvain](https://github.com/jemc)
99
+ * [Lucas Tolchinsky](https://github.com/tonchis)
100
+ * [Matías Flores](https://github.com/matflores)
101
+
102
+ ## Copyright
103
+
104
+ MIT License
105
+
106
+ Copyright (c) 2017 Lautaro Orazi
107
+
108
+ Permission is hereby granted, free of charge, to any person obtaining a copy
109
+ of this software and associated documentation files (the "Software"), to deal
110
+ in the Software without restriction, including without limitation the rights
111
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
112
+ copies of the Software, and to permit persons to whom the Software is
113
+ furnished to do so, subject to the following conditions:
114
+
115
+ The above copyright notice and this permission notice shall be included in all
116
+ copies or substantial portions of the Software.
117
+
118
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
119
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
120
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
121
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
122
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
123
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
124
+ SOFTWARE.
@@ -0,0 +1,15 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "cassandra-schema"
5
+ s.version = "0.1.0"
6
+ s.summary = "Cassandra schema migrations"
7
+ s.license = "MIT"
8
+ s.description = "Simple reversible schema migrations for Cassandra."
9
+ s.authors = ["Lautaro Orazi"]
10
+ s.email = ["orazile@gmail.com"]
11
+ s.homepage = "https://github.com/tarolandia/cassandra-schema"
12
+ s.require_paths = ["lib"]
13
+
14
+ s.files = `git ls-files`.split("\n")
15
+ end
@@ -0,0 +1,16 @@
1
+ version: 2
2
+ jobs:
3
+ build:
4
+ machine: true
5
+ steps:
6
+ - checkout
7
+
8
+ - run:
9
+ name: Install docker-compose
10
+ command: |
11
+ pip install --upgrade pip
12
+ pip install docker-compose
13
+ - run:
14
+ name: Run tests
15
+ command: |
16
+ ./scripts/test
@@ -0,0 +1,31 @@
1
+ CassandraSchema.migration(1) do
2
+ up do
3
+ execute <<~CQL
4
+ CREATE TABLE table_by_name (
5
+ id uuid,
6
+ name text,
7
+ description text,
8
+ PRIMARY KEY (id, name)
9
+ ) WITH CLUSTERING ORDER BY (name ASC)
10
+ CQL
11
+
12
+ execute <<~CQL
13
+ CREATE MATERIALIZED VIEW table_by_description AS
14
+ SELECT
15
+ id,
16
+ name,
17
+ description
18
+ FROM table_by_name
19
+ WHERE id IS NOT NULL
20
+ AND name IS NOT NULL
21
+ AND description IS NOT NULL
22
+ PRIMARY KEY (id, description, name)
23
+ WITH CLUSTERING ORDER BY (description ASC, name ASC)
24
+ CQL
25
+ end
26
+
27
+ down do
28
+ execute "DROP MATERIALIZED VIEW table_by_description"
29
+ execute "DROP TABLE table_by_name"
30
+ end
31
+ end
File without changes
@@ -0,0 +1 @@
1
+ require_relative "cassandra-schema/migration"
@@ -0,0 +1,53 @@
1
+ module CassandraSchema
2
+ @@migrations = {}
3
+
4
+ def self.migrations
5
+ @@migrations
6
+ end
7
+
8
+ def self.reset_migrations!
9
+ @@migrations = {}
10
+ end
11
+
12
+ def self.migration(version, &block)
13
+ fail "Migration version #{version} is already registered" if @@migrations[version]
14
+
15
+ @@migrations[version] = MigrationDSL.new(&block).migration
16
+ end
17
+
18
+ class MigrationDSL
19
+ attr_reader :migration
20
+
21
+ def initialize(&block)
22
+ @migration = Migration.new
23
+ instance_eval(&block)
24
+ end
25
+
26
+ def up(&block)
27
+ @buffer = []
28
+ @migration.set_commands(:up, block.call)
29
+ end
30
+
31
+ def down(&block)
32
+ @buffer = []
33
+ @migration.set_commands(:down, block.call)
34
+ end
35
+
36
+ def execute(command)
37
+ @buffer << command
38
+ end
39
+ end
40
+
41
+ class Migration
42
+ def commands
43
+ @commands ||= {
44
+ up: [],
45
+ down: [],
46
+ }
47
+ end
48
+
49
+ def set_commands(key, _commands)
50
+ commands[key] = _commands
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,141 @@
1
+ require_relative "migration"
2
+
3
+ module CassandraSchema
4
+ class Migrator
5
+ attr_reader :connection, :current_version
6
+
7
+ def initialize(connection:, migrations:, logger: Logger.new(STDOUT))
8
+ @connection = connection
9
+ @logger = logger
10
+ @migrations = migrations
11
+
12
+ generate_migrator_schema!
13
+
14
+ @current_version = get_current_version || init_versioning
15
+ end
16
+
17
+ def migrate(target = nil)
18
+ target ||= @migrations.keys.max || 0
19
+
20
+ @logger.info "Running migrations..."
21
+
22
+ if target == current_version || @migrations.empty?
23
+ @logger.info "Nothing to migrate."
24
+ return
25
+ end
26
+
27
+ begin
28
+ if target > current_version
29
+ # excludes current version's up
30
+ (current_version + 1).upto(target) do |next_version|
31
+ migrate_to(next_version, :up)
32
+ end
33
+ else
34
+ # includes current version's :down
35
+ # excludes target version's :down
36
+ current_version.downto(target + 1) do |version|
37
+ migrate_to(version, :down)
38
+ end
39
+ end
40
+
41
+ @logger.info "Current version: #{current_version}"
42
+ @logger.info "Done!"
43
+ rescue => ex
44
+ @logger.info "Failed migrating all files. Current schema version: #{@current_version}"
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ 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
58
+ end
59
+
60
+ 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
67
+
68
+ def init_versioning
69
+ @connection.execute <<~CQL
70
+ INSERT INTO schema_information(name, value) VALUES('version', '0')
71
+ CQL
72
+
73
+ 0
74
+ end
75
+
76
+ def update_version(target)
77
+ @connection.execute(
78
+ <<~CQL,
79
+ UPDATE schema_information SET value = ? WHERE name = 'version'
80
+ CQL
81
+ arguments: [target.to_s]
82
+ )
83
+
84
+ @current_version = target
85
+ end
86
+
87
+ def migrate_to(target, direction)
88
+ new_version = direction == :up ? target : target - 1
89
+ @logger.info "Migrating to version #{new_version}"
90
+
91
+ unless @migrations[target]
92
+ @logger.info "Missing migration with version #{target}"
93
+ fail
94
+ end
95
+
96
+ # Get commands list
97
+ commands = @migrations.fetch(target).commands.fetch(direction)
98
+ index = 0
99
+
100
+ commands.each do |command|
101
+ unless execute_command(command)
102
+ message = "Failed migrating to version #{target}."
103
+
104
+ if index > 0
105
+ message += " Recovering..."
106
+
107
+ #recover
108
+ recover_commands = @migrations
109
+ .fetch(target)
110
+ .commands
111
+ .fetch(direction == :up ? :down : :up)
112
+ .last(index)
113
+
114
+ results = recover_commands.map { |cmd| execute_command(cmd) }
115
+
116
+ message += results.all? ? "Ok." : "Failed."
117
+ end
118
+
119
+ @logger.info message
120
+
121
+ fail
122
+ end
123
+
124
+ index += 1
125
+ end
126
+
127
+ update_version(new_version)
128
+ end
129
+
130
+ def execute_command(command)
131
+ begin
132
+ @connection.execute command
133
+ true
134
+ rescue => ex
135
+ @logger.error ex.message
136
+ false
137
+ end
138
+ end
139
+
140
+ end
141
+ end
@@ -0,0 +1,3 @@
1
+ module CassandraSchema
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1 @@
1
+ 2.4.1
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env sh
2
+
3
+ # Move to the base project directory if not there already.
4
+ cd "$(dirname "$0")"/..
5
+
6
+ # Make sure the latest test image is built.
7
+ docker build -t cassandra/schema:test -f Dockerfile.test .
8
+
9
+ # Run the given tests in the test container.
10
+ docker-compose -p cassandraschematest -f test/docker-compose.yml run --rm test \
11
+ sh -c "scripts/test-run ${@}"
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ usage = <<-USAGE
4
+ Usage:
5
+
6
+ scripts/test-run --help # Show this help text
7
+ scripts/test-run # Run all tests in test/**/*.rb
8
+ scripts/test-run DIR # Run all tests in test/DIR/**/*.rb
9
+ scripts/test-run FILE1.rb FILE2.rb # Run all tests in FILE1.rb and FILE2.rb
10
+ USAGE
11
+
12
+ TEST_DIR = File.expand_path("../test", File.dirname(__FILE__))
13
+ TEST_SEED = ENV.fetch("TEST_SEED", rand(99999)).to_i
14
+ TEST_FILES = case ARGV.first
15
+ when "--help" then warn usage; exit 0
16
+ when nil then Dir["#{TEST_DIR}/**/*.rb"]
17
+ when %r(\A[\w/]+\z) then Dir["#{TEST_DIR}/#{ARGV.first}/**/*.rb"]
18
+ else ARGV.map { |file| "#{TEST_DIR}/#{file}" }
19
+ end
20
+
21
+ puts "Loading test files with TEST_SEED=#{TEST_SEED}"
22
+ puts
23
+
24
+ TEST_FILES.shuffle(random: Random.new(TEST_SEED)).each { |rb| require rb }
@@ -0,0 +1,21 @@
1
+ ---
2
+ version: '2'
3
+
4
+ services:
5
+ cassandra:
6
+ image: cassandra:3.11
7
+
8
+ test:
9
+ command: tail -f /dev/null # wait for tests to be run via docker exec
10
+ image: cassandra/schema:test
11
+ working_dir: /opt/test
12
+ volumes:
13
+ - ${PWD}:/opt/test
14
+ links:
15
+ - cassandra
16
+ environment:
17
+ CASSANDRA_USERNAME: cassandra
18
+ CASSANDRA_PASSWORD: cassandra
19
+ CASSANDRA_HOSTS: cassandra
20
+ CASSANDRA_PORT: 9042
21
+ CASSANDRA_KEYSPACE: cassandra_schema_test
@@ -0,0 +1,33 @@
1
+ require "minitest/autorun"
2
+
3
+ require_relative "../lib/cassandra-schema/migration"
4
+
5
+ describe "CassandraSchema::Migration" do
6
+ before do
7
+ @migration = CassandraSchema::Migration.new
8
+ end
9
+
10
+ it "returns empty commands hash" do
11
+ assert_equal(
12
+ {
13
+ up: [],
14
+ down: [],
15
+ },
16
+ @migration.commands
17
+ )
18
+ end
19
+
20
+ it "sets :up commands" do
21
+ commands = ["CQL command 1", "CQL command 2"]
22
+ @migration.set_commands(:up, commands)
23
+
24
+ assert_equal commands, @migration.commands.fetch(:up)
25
+ end
26
+
27
+ it "sets :down commands" do
28
+ commands = ["CQL command 1", "CQL command 2"]
29
+ @migration.set_commands(:down, commands)
30
+
31
+ assert_equal commands, @migration.commands.fetch(:down)
32
+ end
33
+ end
@@ -0,0 +1,60 @@
1
+ require "minitest/autorun"
2
+
3
+ require_relative "../lib/cassandra-schema/migration"
4
+
5
+ describe "CassandraSchema" do
6
+ before do
7
+ CassandraSchema.reset_migrations!
8
+
9
+ CassandraSchema.migration(1) do; end
10
+ CassandraSchema.migration(2) do; end
11
+ end
12
+
13
+ it "registers migrations" do
14
+ assert_equal 2, CassandraSchema.migrations.size
15
+
16
+ CassandraSchema.migrations.each_with_index do |(version, migration), index|
17
+ assert_equal index + 1, version
18
+ assert_instance_of CassandraSchema::Migration, migration
19
+ end
20
+ end
21
+
22
+ it "resets migrations" do
23
+ CassandraSchema.reset_migrations!
24
+
25
+ assert_equal 0, CassandraSchema.migrations.size
26
+ end
27
+
28
+ it "fails adding migration with the same version number" do
29
+ assert_raises(RuntimeError, "Migration version 2 is already registered") do
30
+ CassandraSchema.migration(2) do; end
31
+ end
32
+ end
33
+
34
+ describe "DSL" do
35
+ it "generates migration with up and down commands" do
36
+ CassandraSchema.migration(3) do
37
+ up do
38
+ execute "CQL command 1"
39
+ execute "CQL command 2"
40
+ end
41
+
42
+ down do
43
+ execute "CQL revert command 2"
44
+ execute "CQL revert command 1"
45
+ end
46
+ end
47
+
48
+ migration = CassandraSchema.migrations[3]
49
+
50
+ assert migration
51
+ assert_equal(
52
+ {
53
+ up: ["CQL command 1", "CQL command 2"],
54
+ down: ["CQL revert command 2", "CQL revert command 1"],
55
+ },
56
+ migration.commands
57
+ )
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,159 @@
1
+ require "minitest/autorun"
2
+
3
+ require_relative "support/connections"
4
+ require_relative "../lib/cassandra-schema/migrator"
5
+
6
+ class FakeLogger
7
+ attr_reader :stdout
8
+
9
+ def initialize
10
+ @stdout = []
11
+ end
12
+
13
+ def info(value)
14
+ @stdout << value
15
+ end
16
+
17
+ def error(value)
18
+ @stdout << value
19
+ end
20
+ end
21
+
22
+ CONN = Connections::Cassandra.create_with_retries
23
+ CONN.execute <<~CQL
24
+ CREATE KEYSPACE IF NOT EXISTS cassandra_schema_migrator
25
+ WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };
26
+ CQL
27
+
28
+ CONN.execute "USE cassandra_schema_migrator"
29
+
30
+ describe "CassandraSchema::Migrator" do
31
+ before do
32
+ CONN.execute "DROP TABLE IF EXISTS schema_information"
33
+ CONN.execute "DROP MATERIALIZED VIEW IF EXISTS table_by_description"
34
+ CONN.execute "DROP TABLE IF EXISTS table_by_name"
35
+
36
+ CassandraSchema.reset_migrations!
37
+
38
+ CassandraSchema.migration(1) do
39
+ up do
40
+ execute <<~CQL
41
+ CREATE TABLE table_by_name (
42
+ id uuid,
43
+ name text,
44
+ description text,
45
+ PRIMARY KEY (id, name)
46
+ ) WITH CLUSTERING ORDER BY (name ASC)
47
+ CQL
48
+
49
+ execute <<~CQL
50
+ CREATE MATERIALIZED VIEW table_by_description AS
51
+ SELECT
52
+ id,
53
+ name,
54
+ description
55
+ FROM table_by_name
56
+ WHERE id IS NOT NULL
57
+ AND name IS NOT NULL
58
+ AND description IS NOT NULL
59
+ PRIMARY KEY (id, description, name)
60
+ WITH CLUSTERING ORDER BY (description ASC, name ASC)
61
+ CQL
62
+ end
63
+
64
+ down do
65
+ execute "DROP MATERIALIZED VIEW table_by_description"
66
+ execute "DROP TABLE table_by_name"
67
+ end
68
+ end
69
+
70
+ CassandraSchema.migration(2) do
71
+ up do
72
+ execute "ALTER TABLE table_by_name ADD email text"
73
+ execute "ALTER TABLE table_by_name ADD alt_email text"
74
+ end
75
+
76
+ down do
77
+ execute "ALTER TABLE table_by_name DROP alt_email"
78
+ execute "ALTER TABLE table_by_name DROP email"
79
+ end
80
+ end
81
+
82
+ @fake_logger = FakeLogger.new
83
+ end
84
+
85
+ it "initializes schema_information table if not exists" do
86
+ migrator = CassandraSchema::Migrator.new(
87
+ connection: CONN,
88
+ migrations: {},
89
+ logger: @fake_logger,
90
+ )
91
+
92
+ assert_equal 0, migrator.current_version
93
+ end
94
+
95
+ describe "migrating up" do
96
+ it "migrates to last version" do
97
+ migrator = CassandraSchema::Migrator.new(
98
+ connection: CONN,
99
+ migrations: CassandraSchema.migrations,
100
+ logger: @fake_logger,
101
+ )
102
+
103
+ migrator.migrate
104
+
105
+ assert_equal 2, migrator.current_version
106
+ end
107
+
108
+ it "migrates to target version" do
109
+ migrator = CassandraSchema::Migrator.new(
110
+ connection: CONN,
111
+ migrations: CassandraSchema.migrations,
112
+ logger: @fake_logger,
113
+ )
114
+
115
+ migrator.migrate(1)
116
+
117
+ assert_equal 1, migrator.current_version
118
+ end
119
+
120
+ it "fails if there is a missing version" do
121
+ CassandraSchema.migration(4) do
122
+ up do
123
+ execute "ALTER TABLE users DROP email"
124
+ end
125
+
126
+ down do
127
+ execute "ALTER TABLE users ADD email text"
128
+ end
129
+ end
130
+
131
+ migrator = CassandraSchema::Migrator.new(
132
+ connection: CONN,
133
+ migrations: CassandraSchema.migrations,
134
+ logger: @fake_logger,
135
+ )
136
+
137
+ migrator.migrate
138
+ assert_equal "Failed migrating all files. Current schema version: 2", @fake_logger.stdout.pop
139
+ assert_equal "Missing migration with version 3", @fake_logger.stdout.pop
140
+ end
141
+ end
142
+
143
+ describe "migrating down" do
144
+ before do
145
+ @migrator = CassandraSchema::Migrator.new(
146
+ connection: CONN,
147
+ migrations: CassandraSchema.migrations,
148
+ logger: @fake_logger,
149
+ )
150
+ # Start on version 2
151
+ @migrator.migrate
152
+ end
153
+
154
+ it "migrates to target version" do
155
+ @migrator.migrate(1)
156
+ assert_equal 1, @migrator.current_version
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,51 @@
1
+ require "cassandra"
2
+ require "lz4-ruby"
3
+
4
+ module Connections
5
+ def self.create_with_retries(mod, *args)
6
+ waits = [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
7
+ begin
8
+ Connections::Cassandra.create(*args)
9
+
10
+ rescue *mod::CONNECTION_ERRORS
11
+ wait = waits.shift
12
+ fail "Gave up on connecting to #{mod}" if wait.nil?
13
+
14
+ puts "Couldn't connect to #{mod}; retrying in #{wait} seconds"
15
+ sleep wait
16
+ retry
17
+ end
18
+ end
19
+
20
+ module Cassandra
21
+ DEFAULT_USERNAME = ENV.fetch("CASSANDRA_USERNAME")
22
+ DEFAULT_PASSWORD = ENV.fetch("CASSANDRA_PASSWORD")
23
+ DEFAULT_HOSTS = ENV.fetch("CASSANDRA_HOSTS").strip.split(" ")
24
+ DEFAULT_PORT = ENV.fetch("CASSANDRA_PORT", "9042").to_i
25
+ DEFAULT_COMPRESSION = :lz4
26
+
27
+ CONNECTION_ERRORS = [::Cassandra::Errors::NoHostsAvailable]
28
+
29
+ class << self
30
+ attr_accessor :current
31
+ end
32
+
33
+ def self.create(options = {})
34
+ options = {
35
+ username: DEFAULT_USERNAME,
36
+ password: DEFAULT_PASSWORD,
37
+ hosts: DEFAULT_HOSTS,
38
+ port: DEFAULT_PORT,
39
+ compression: DEFAULT_COMPRESSION,
40
+ }.merge(options)
41
+
42
+ keyspace = options.delete(:keyspace)
43
+
44
+ ::Cassandra.cluster(options).connect(keyspace)
45
+ end
46
+
47
+ def self.create_with_retries(*args)
48
+ Connections.create_with_retries(self, *args)
49
+ end
50
+ end
51
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cassandra-schema
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Lautaro Orazi
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-09-15 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Simple reversible schema migrations for Cassandra.
14
+ email:
15
+ - orazile@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".gems-test"
21
+ - ".gitignore"
22
+ - Dockerfile.test
23
+ - LICENSE
24
+ - README.md
25
+ - cassandra-schema.gemspec
26
+ - circle.yml
27
+ - examples/migration.rb
28
+ - lib/.gitkeep
29
+ - lib/cassandra-schema.rb
30
+ - lib/cassandra-schema/migration.rb
31
+ - lib/cassandra-schema/migrator.rb
32
+ - lib/cassandra-schema/version.rb
33
+ - ruby-version.sample
34
+ - scripts/test
35
+ - scripts/test-run
36
+ - test/docker-compose.yml
37
+ - test/migration_test.rb
38
+ - test/migrations_test.rb
39
+ - test/migrator_test.rb
40
+ - test/support/connections.rb
41
+ homepage: https://github.com/tarolandia/cassandra-schema
42
+ licenses:
43
+ - MIT
44
+ metadata: {}
45
+ post_install_message:
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubyforge_project:
61
+ rubygems_version: 2.6.11
62
+ signing_key:
63
+ specification_version: 4
64
+ summary: Cassandra schema migrations
65
+ test_files: []