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