cassandra-schema 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []