cassandra_migrations 0.0.1.pre0
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.
- data/lib/cassandra_migrations.rb +5 -0
- data/lib/cassandra_migrations/cassandra.rb +95 -0
- data/lib/cassandra_migrations/cassandra/errors.rb +42 -0
- data/lib/cassandra_migrations/cassandra/migrator.rb +83 -0
- data/lib/cassandra_migrations/cassandra/query.rb +44 -0
- data/lib/cassandra_migrations/railtie.rb +17 -0
- data/lib/cassandra_migrations/tasks/cassandra.rake +74 -0
- metadata +132 -0
@@ -0,0 +1,95 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require 'cql'
|
5
|
+
require 'cassandra_migrations/cassandra/query'
|
6
|
+
require 'cassandra_migrations/cassandra/errors'
|
7
|
+
require 'cassandra_migrations/cassandra/migrator'
|
8
|
+
|
9
|
+
module CassandraMigrations::Cassandra
|
10
|
+
extend Query
|
11
|
+
|
12
|
+
mattr_accessor :client
|
13
|
+
mattr_accessor :config
|
14
|
+
|
15
|
+
def self.start!
|
16
|
+
connect_to_server unless client
|
17
|
+
|
18
|
+
# setup keyspace use
|
19
|
+
begin
|
20
|
+
use(config['keyspace'])
|
21
|
+
rescue Cql::QueryError # keyspace does not exist
|
22
|
+
raise Errors::UnexistingKeyspaceError, config['keyspace']
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.shutdown!
|
27
|
+
if client
|
28
|
+
client.shutdown!
|
29
|
+
self.client = nil
|
30
|
+
end
|
31
|
+
|
32
|
+
self.config = nil if config
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.create_keyspace!
|
36
|
+
connect_to_server unless client
|
37
|
+
|
38
|
+
begin
|
39
|
+
execute(
|
40
|
+
"CREATE KEYSPACE #{config['keyspace']} \
|
41
|
+
WITH replication = { \
|
42
|
+
'class':'#{config['replication']['class']}', \
|
43
|
+
'replication_factor': #{config['replication']['replication_factor']} \
|
44
|
+
}"
|
45
|
+
)
|
46
|
+
use(config['keyspace'])
|
47
|
+
execute("CREATE TABLE metadata (data_name varchar PRIMARY KEY, data_value varchar)")
|
48
|
+
write("metadata", {:data_name => 'version', :data_value => '0'})
|
49
|
+
rescue Exception => exception
|
50
|
+
drop!
|
51
|
+
raise exception
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.drop!
|
56
|
+
connect_to_server unless client
|
57
|
+
|
58
|
+
begin
|
59
|
+
execute("DROP KEYSPACE #{config['keyspace']}")
|
60
|
+
rescue Cql::QueryError
|
61
|
+
raise Errors::UnexistingKeyspaceError, config['keyspace']
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.use(keyspace)
|
66
|
+
raise Errors::ClientNotStartedError unless client
|
67
|
+
client.use(keyspace)
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.execute(cql)
|
71
|
+
raise Errors::ClientNotStartedError unless client
|
72
|
+
client.execute(cql)
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def self.connect_to_server
|
78
|
+
load_config
|
79
|
+
|
80
|
+
begin
|
81
|
+
self.client = Cql::Client.new(:host => config['host'], :port => config['port'])
|
82
|
+
client.start!
|
83
|
+
rescue Cql::Io::ConnectionError => e
|
84
|
+
raise Errors::ConnectionError, e.message
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.load_config
|
89
|
+
begin
|
90
|
+
self.config = YAML.load_file(Rails.root.join("config", "cassandra.yml"))[Rails.env]
|
91
|
+
rescue Errno::ENOENT
|
92
|
+
raise Errors::MissingConfigurationError
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module CassandraMigrations::Cassandra
|
4
|
+
module Errors
|
5
|
+
|
6
|
+
class CassandraError < StandardError
|
7
|
+
end
|
8
|
+
|
9
|
+
class ClientNotStartedError < CassandraError
|
10
|
+
def initialize
|
11
|
+
super("Cassandra.start has not been called yet! Can't execute queries before connecting to server...")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class MissingConfigurationError < CassandraError
|
16
|
+
def initialize
|
17
|
+
super("config/cassandra.yml is missing use this as example: \n\
|
18
|
+
development: \n\
|
19
|
+
host: '127.0.0.1' \n\
|
20
|
+
port: 9042 \n\
|
21
|
+
keyspace: 'lme_smart_grid_server_development' \n\
|
22
|
+
replication: \n\
|
23
|
+
class: 'SimpleStrategy' \n\
|
24
|
+
replication_factor: 1
|
25
|
+
")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class UnexistingKeyspaceError < CassandraError
|
30
|
+
def initialize(keyspace)
|
31
|
+
super("Keyspace #{keyspace} does not exist. Run rake cassandra:create.")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class ConnectionError < CassandraError
|
36
|
+
def initialize(msg)
|
37
|
+
super(msg)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module CassandraMigrations::Cassandra
|
4
|
+
module Migrator
|
5
|
+
|
6
|
+
def self.up_to_latest
|
7
|
+
current_version = read_current_version
|
8
|
+
|
9
|
+
new_migrations = get_all_migration_names.sort.select do |migration_name|
|
10
|
+
get_version_from_migration_name(migration_name) > current_version
|
11
|
+
end
|
12
|
+
|
13
|
+
if !new_migrations.empty?
|
14
|
+
new_migrations.each { |migration| up(migration) }
|
15
|
+
end
|
16
|
+
|
17
|
+
new_migrations.size
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.rollback(count=1)
|
21
|
+
current_version = read_current_version
|
22
|
+
|
23
|
+
executed_migrations = get_all_migration_names.sort.reverse.select do |migration_name|
|
24
|
+
get_version_from_migration_name(migration_name) <= current_version
|
25
|
+
end
|
26
|
+
|
27
|
+
down_count = 0
|
28
|
+
|
29
|
+
if !executed_migrations.empty?
|
30
|
+
count.times do |i|
|
31
|
+
if executed_migrations[i]
|
32
|
+
down(executed_migrations[i], executed_migrations[i])
|
33
|
+
down_count += 1
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
down_count
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.read_current_version
|
42
|
+
CassandraMigrations::Cassandra.select("metadata", :selection => "data_name='version'", :projection => 'data_value').first['data_value'].to_i
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def self.up(migration_name)
|
48
|
+
# load migration
|
49
|
+
require migration_name
|
50
|
+
# run migration
|
51
|
+
get_class_from_migration_name(migration_name).up
|
52
|
+
|
53
|
+
# update version
|
54
|
+
CassandraMigrations::Cassandra.write("metadata", {:data_name => 'version', :data_value => get_version_from_migration_name(migration_name).to_s})
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.down(migration_name, previous_migration_name=nil)
|
58
|
+
# load migration
|
59
|
+
require migration_name
|
60
|
+
# run migration
|
61
|
+
get_class_from_migration_name(migration_name).down
|
62
|
+
|
63
|
+
# downgrade version
|
64
|
+
if previous_migration_name
|
65
|
+
CassandraMigrations::Cassandra.write("metadata", {:data_name => 'version', :data_value => get_version_from_migration_name(previous_migration_name).to_s})
|
66
|
+
else
|
67
|
+
CassandraMigrations::Cassandra.write("metadata", {:data_name => 'version', :data_value => '0'})
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.get_all_migration_names
|
72
|
+
Dir[Rails.root.join("db", "cassandra_migrate/[0-9]*_*.rb")]
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.get_class_from_migration_name(filename)
|
76
|
+
filename.match(/[0-9]{14}_(.+)\.rb$/).captures.first.camelize.constantize
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.get_version_from_migration_name(migration_name)
|
80
|
+
migration_name.match(/([0-9]{14})_.+\.rb$/).captures.first.to_i
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module CassandraMigrations::Cassandra
|
4
|
+
module Query
|
5
|
+
|
6
|
+
def write(table, hash)
|
7
|
+
columns = []
|
8
|
+
values = []
|
9
|
+
|
10
|
+
hash.each do |k,v|
|
11
|
+
columns << k.to_s
|
12
|
+
values << "'#{v.to_s}'"
|
13
|
+
end
|
14
|
+
|
15
|
+
execute("INSERT INTO #{table} (#{columns.join(', ')}) VALUES (#{values.join(', ')})")
|
16
|
+
end
|
17
|
+
|
18
|
+
def select(table, options={})
|
19
|
+
query_string = "SELECT #{options[:projection] || '*'} FROM #{table}"
|
20
|
+
|
21
|
+
if options[:selection]
|
22
|
+
query_string << " WHERE #{options[:selection]}"
|
23
|
+
end
|
24
|
+
|
25
|
+
if options[:order_by]
|
26
|
+
query_string << " ORDER BY #{options[:order_by]}"
|
27
|
+
end
|
28
|
+
|
29
|
+
if options[:limit]
|
30
|
+
query_string << " LIMIT #{options[:limit]}"
|
31
|
+
end
|
32
|
+
|
33
|
+
execute(query_string)
|
34
|
+
end
|
35
|
+
|
36
|
+
def delete(table, selection, options={})
|
37
|
+
execute("DELETE #{options[:projection]} FROM #{table} WHERE #{selection}")
|
38
|
+
end
|
39
|
+
|
40
|
+
def truncate(table)
|
41
|
+
execute("TRUNCATE #{table}")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# encoding : utf-8
|
2
|
+
|
3
|
+
require 'cassandra_migrations/cassandra'
|
4
|
+
|
5
|
+
class CassandraMigrations::Railtie < ::Rails::Railtie
|
6
|
+
|
7
|
+
initializer "cassandra_migrations.start" do
|
8
|
+
CassandraMigrations::Cassandra.start!
|
9
|
+
end
|
10
|
+
|
11
|
+
rake_tasks do
|
12
|
+
Dir[File.expand_path("tasks/**/*.rake", File.dirname(__FILE__))].each do |file|
|
13
|
+
load file
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# encoding : utf-8
|
2
|
+
|
3
|
+
namespace :cassandra do
|
4
|
+
|
5
|
+
task :start do
|
6
|
+
CassandraMigrations::Cassandra.start!
|
7
|
+
end
|
8
|
+
|
9
|
+
desc 'Create the keyspace in config/cassandra.yml for the current environment'
|
10
|
+
task :create do
|
11
|
+
begin
|
12
|
+
CassandraMigrations::Cassandra.start!
|
13
|
+
puts "Keyspace #{CassandraMigrations::Cassandra.config['keyspace']} already exists!"
|
14
|
+
rescue CassandraMigrations::Cassandra::Errors::UnexistingKeyspaceError
|
15
|
+
CassandraMigrations::Cassandra.create_keyspace!
|
16
|
+
puts "Created keyspace #{CassandraMigrations::Cassandra.config['keyspace']}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
desc 'Drop keyspace in config/cassandra.yml for the current environment'
|
21
|
+
task :drop do
|
22
|
+
begin
|
23
|
+
CassandraMigrations::Cassandra.drop!
|
24
|
+
puts "Dropped keyspace #{CassandraMigrations::Cassandra.config['keyspace']}"
|
25
|
+
rescue CassandraMigrations::Cassandra::Errors::UnexistingKeyspaceError
|
26
|
+
puts "Keyspace #{CassandraMigrations::Cassandra.config['keyspace']} does not exist already... cannot be dropped"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
desc 'Migrate the keyspace to the latest version'
|
31
|
+
task :migrate => :start do
|
32
|
+
migrations_up_count = CassandraMigrations::Cassandra::Migrator.up_to_latest
|
33
|
+
|
34
|
+
if migrations_up_count == 0
|
35
|
+
puts "Already up-to-date"
|
36
|
+
else
|
37
|
+
puts "Migrated #{migrations_up_count} version(s) up."
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
desc 'Rolls the schema back to the previous version (specify steps w/ STEP=n)'
|
42
|
+
task :rollback => :start do
|
43
|
+
steps = ENV['STEP'] ? ENV['STEP'].to_i : 1
|
44
|
+
|
45
|
+
migrations_down_count = CassandraMigrations::Cassandra::Migrator.rollback(steps)
|
46
|
+
|
47
|
+
if steps == migrations_down_count
|
48
|
+
puts "Rolled back #{steps} version(s)."
|
49
|
+
else
|
50
|
+
puts "Asked to rollback #{steps} version(s). Only achieved #{migrations_down_count}."
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
desc 'Resets and prepares cassandra database (all data will be lost)'
|
55
|
+
task :setup do
|
56
|
+
Rake::Task['cassandra:drop'].execute
|
57
|
+
Rake::Task['cassandra:create'].execute
|
58
|
+
Rake::Task['cassandra:migrate'].execute
|
59
|
+
end
|
60
|
+
|
61
|
+
namespace :test do
|
62
|
+
desc 'Load the development schema in to the test keyspace'
|
63
|
+
task :prepare do
|
64
|
+
Rails.env = 'test'
|
65
|
+
Rake::Task['cassandra:setup'].execute
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
desc 'Retrieves the current schema version number'
|
70
|
+
task :version => :start do
|
71
|
+
puts "Current version: #{Cassandra::Migrator.read_current_version}"
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
metadata
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cassandra_migrations
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1.pre0
|
5
|
+
prerelease: 6
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Henrique Gubert
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-03-29 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: cql-rb
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - '='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.0.0.pre3
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - '='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.0.0.pre3
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '10'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '10'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rails
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '3.2'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.2'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rspec
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: debugger
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
description: A gem to manage Cassandra database schema for Rails. This gem offers
|
95
|
+
migrations and environment specific databases out-of-the-box for Rails users.
|
96
|
+
email: guberthenrique@hotmail.com
|
97
|
+
executables: []
|
98
|
+
extensions: []
|
99
|
+
extra_rdoc_files: []
|
100
|
+
files:
|
101
|
+
- lib/cassandra_migrations/cassandra.rb
|
102
|
+
- lib/cassandra_migrations/cassandra/errors.rb
|
103
|
+
- lib/cassandra_migrations/cassandra/migrator.rb
|
104
|
+
- lib/cassandra_migrations/cassandra/query.rb
|
105
|
+
- lib/cassandra_migrations/tasks/cassandra.rake
|
106
|
+
- lib/cassandra_migrations/railtie.rb
|
107
|
+
- lib/cassandra_migrations.rb
|
108
|
+
homepage: https://github.com/hsgubert/cassandra_migrations
|
109
|
+
licenses: []
|
110
|
+
post_install_message:
|
111
|
+
rdoc_options: []
|
112
|
+
require_paths:
|
113
|
+
- lib
|
114
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
115
|
+
none: false
|
116
|
+
requirements:
|
117
|
+
- - ! '>='
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: '0'
|
120
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: 1.8.0
|
126
|
+
requirements: []
|
127
|
+
rubyforge_project:
|
128
|
+
rubygems_version: 1.8.24
|
129
|
+
signing_key:
|
130
|
+
specification_version: 3
|
131
|
+
summary: Cassandra schema management for a multi-environment developer.
|
132
|
+
test_files: []
|