active_column 0.1.1 → 0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/.rvmrc +1 -7
- data/Gemfile.lock +60 -61
- data/README.md +7 -3
- data/active_column.gemspec +2 -3
- data/docs/Migrate.md +30 -7
- data/generators/ks_migration/USAGE +6 -0
- data/generators/ks_migration/ks_migration_generator.rb +17 -0
- data/generators/ks_migration/templates/migration.rb.erb +11 -0
- data/lib/active_column.rb +16 -9
- data/lib/active_column/configuration.rb +36 -0
- data/lib/active_column/helpers.rb +36 -0
- data/lib/active_column/migration.rb +76 -214
- data/lib/active_column/migrator.rb +231 -0
- data/lib/active_column/tasks/column_family.rb +28 -17
- data/lib/active_column/tasks/keyspace.rb +38 -3
- data/lib/active_column/tasks/ks.rake +142 -0
- data/lib/active_column/version.rb +1 -1
- data/spec/active_column/migration_spec.rb +58 -0
- data/spec/active_column/tasks/column_family_spec.rb +4 -2
- data/spec/active_column/tasks/keyspace_spec.rb +104 -4
- data/spec/spec_helper.rb +36 -11
- metadata +24 -44
- data/lib/active_column/connection.rb +0 -15
- data/lib/active_column/tasks/ks.rb +0 -76
@@ -9,39 +9,51 @@ module ActiveColumn
|
|
9
9
|
:long => 'LongType',
|
10
10
|
:string => 'BytesType' }
|
11
11
|
|
12
|
-
def initialize
|
13
|
-
|
12
|
+
def initialize(keyspace)
|
13
|
+
raise 'Cannot operate on system keyspace' if keyspace == 'system'
|
14
|
+
@keyspace = keyspace
|
14
15
|
end
|
15
16
|
|
16
17
|
def exists?(name)
|
17
|
-
|
18
|
+
connection.schema.cf_defs.find { |cf_def| cf_def.name == name.to_s }
|
18
19
|
end
|
19
20
|
|
20
|
-
def create(name,
|
21
|
-
|
22
|
-
|
23
|
-
|
21
|
+
def create(name, &block)
|
22
|
+
cf = Cassandra::ColumnFamily.new
|
23
|
+
cf.name = name.to_s
|
24
|
+
cf.keyspace = @keyspace.to_s
|
25
|
+
cf.comparator_type = 'TimeUUIDType'
|
24
26
|
|
25
|
-
cf
|
26
|
-
|
27
|
+
block.call cf if block
|
28
|
+
|
29
|
+
post_process_column_family(cf)
|
30
|
+
connection.add_column_family(cf)
|
27
31
|
end
|
28
32
|
|
29
33
|
def drop(name)
|
30
|
-
|
34
|
+
connection.drop_column_family(name.to_s)
|
35
|
+
end
|
36
|
+
|
37
|
+
def rename(old_name, new_name)
|
38
|
+
connection.rename_column_family(old_name.to_s, new_name.to_s)
|
31
39
|
end
|
32
40
|
|
33
41
|
def clear(name)
|
34
|
-
|
42
|
+
connection.truncate!(name.to_s)
|
35
43
|
end
|
36
44
|
|
37
45
|
private
|
38
46
|
|
39
|
-
def
|
40
|
-
|
47
|
+
def connection
|
48
|
+
ActiveColumn.connection
|
49
|
+
end
|
50
|
+
|
51
|
+
def post_process_column_family(cf)
|
52
|
+
type = cf.comparator_type
|
41
53
|
if type && COMPARATOR_TYPES.has_key?(type)
|
42
|
-
|
54
|
+
cf.comparator_type = COMPARATOR_TYPES[type]
|
43
55
|
end
|
44
|
-
|
56
|
+
cf
|
45
57
|
end
|
46
58
|
|
47
59
|
end
|
@@ -54,11 +66,10 @@ class Cassandra
|
|
54
66
|
class ColumnFamily
|
55
67
|
def with_fields(options)
|
56
68
|
struct_fields.collect { |f| f[1][:name] }.each do |f|
|
57
|
-
send("#{f}=", options[f.to_sym])
|
69
|
+
send("#{f}=", options[f.to_sym] || options[f.to_s])
|
58
70
|
end
|
59
71
|
self
|
60
72
|
end
|
61
73
|
end
|
62
74
|
end
|
63
75
|
|
64
|
-
|
@@ -3,6 +3,16 @@ module ActiveColumn
|
|
3
3
|
module Tasks
|
4
4
|
|
5
5
|
class Keyspace
|
6
|
+
include ActiveColumn::Helpers
|
7
|
+
|
8
|
+
def self.parse(hash)
|
9
|
+
ks = Cassandra::Keyspace.new.with_fields hash
|
10
|
+
ks.cf_defs = []
|
11
|
+
hash['cf_defs'].each do |cf|
|
12
|
+
ks.cf_defs << Cassandra::ColumnFamily.new.with_fields(cf)
|
13
|
+
end
|
14
|
+
ks
|
15
|
+
end
|
6
16
|
|
7
17
|
def initialize
|
8
18
|
c = ActiveColumn.connection
|
@@ -14,6 +24,11 @@ module ActiveColumn
|
|
14
24
|
end
|
15
25
|
|
16
26
|
def create(name, options = {})
|
27
|
+
if exists? name
|
28
|
+
log "Keyspace '#{name}' already exists - cannot create"
|
29
|
+
return nil
|
30
|
+
end
|
31
|
+
|
17
32
|
opts = { :name => name.to_s,
|
18
33
|
:strategy_class => 'org.apache.cassandra.locator.LocalStrategy',
|
19
34
|
:replication_factor => 1,
|
@@ -21,13 +36,18 @@ module ActiveColumn
|
|
21
36
|
|
22
37
|
ks = Cassandra::Keyspace.new.with_fields(opts)
|
23
38
|
@cassandra.add_keyspace ks
|
39
|
+
ks
|
24
40
|
end
|
25
41
|
|
26
42
|
def drop(name)
|
43
|
+
return log 'Cannot drop system keyspace' if name == 'system'
|
44
|
+
return log "Keyspace '#{name}' does not exist - cannot drop" if !exists? name
|
27
45
|
@cassandra.drop_keyspace name.to_s
|
46
|
+
true
|
28
47
|
end
|
29
48
|
|
30
49
|
def set(name)
|
50
|
+
return log "Keyspace '#{name}' does not exist - cannot set" if !exists? name
|
31
51
|
@cassandra.keyspace = name.to_s
|
32
52
|
end
|
33
53
|
|
@@ -36,11 +56,26 @@ module ActiveColumn
|
|
36
56
|
end
|
37
57
|
|
38
58
|
def clear
|
39
|
-
return
|
40
|
-
|
59
|
+
return log 'Cannot clear system keyspace' if @cassandra.keyspace == 'system'
|
41
60
|
@cassandra.clear_keyspace!
|
42
61
|
end
|
43
62
|
|
63
|
+
def schema_dump
|
64
|
+
@cassandra.schema
|
65
|
+
end
|
66
|
+
|
67
|
+
def schema_load(schema)
|
68
|
+
@cassandra.schema.cf_defs.each do |cf|
|
69
|
+
@cassandra.drop_column_family cf.name
|
70
|
+
end
|
71
|
+
|
72
|
+
keyspace = get
|
73
|
+
schema.cf_defs.each do |cf|
|
74
|
+
cf.keyspace = keyspace
|
75
|
+
@cassandra.add_column_family cf
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
44
79
|
end
|
45
80
|
|
46
81
|
end
|
@@ -51,7 +86,7 @@ class Cassandra
|
|
51
86
|
class Keyspace
|
52
87
|
def with_fields(options)
|
53
88
|
struct_fields.collect { |f| f[1][:name] }.each do |f|
|
54
|
-
send("#{f}=", options[f.to_sym])
|
89
|
+
send("#{f}=", options[f.to_sym] || options[f.to_s])
|
55
90
|
end
|
56
91
|
self
|
57
92
|
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'active_column/tasks/keyspace'
|
3
|
+
require 'active_column/tasks/column_family'
|
4
|
+
|
5
|
+
namespace :ks do
|
6
|
+
|
7
|
+
if defined? ::Rails
|
8
|
+
task :configure => :environment do
|
9
|
+
configure
|
10
|
+
end
|
11
|
+
else
|
12
|
+
task :configure do
|
13
|
+
configure
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
task :set_keyspace => :configure do
|
18
|
+
set_keyspace
|
19
|
+
end
|
20
|
+
|
21
|
+
desc 'Create the keyspace in config/cassandra.yml for the current environment'
|
22
|
+
task :create => :configure do
|
23
|
+
ks = ActiveColumn::Tasks::Keyspace.new.create @config['keyspace'], @config
|
24
|
+
puts "Created keyspace: #{@config['keyspace']}" if ks
|
25
|
+
end
|
26
|
+
|
27
|
+
namespace :create do
|
28
|
+
desc 'Create keyspaces in config/cassandra.yml for all environments'
|
29
|
+
task :all => :configure do
|
30
|
+
@configs.values.each do |config|
|
31
|
+
ks = ActiveColumn::Tasks::Keyspace.new.create config['keyspace'], config
|
32
|
+
puts "Created keyspace: #{config['keyspace']}" if ks
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
desc 'Drop keyspace in config/cassandra.yml for the current environment'
|
38
|
+
task :drop => :configure do
|
39
|
+
dropped = ActiveColumn::Tasks::Keyspace.new.drop @config['keyspace']
|
40
|
+
puts "Dropped keyspace: #{@config['keyspace']}" if dropped
|
41
|
+
end
|
42
|
+
|
43
|
+
namespace :drop do
|
44
|
+
desc 'Drop keyspaces in config/cassandra.yml for all environments'
|
45
|
+
task :all => :configure do
|
46
|
+
@configs.values.each do |config|
|
47
|
+
dropped = ActiveColumn::Tasks::Keyspace.new.drop config['keyspace']
|
48
|
+
puts "Dropped keyspace: #{config['keyspace']}" if dropped
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
desc 'Migrate the keyspace (options: VERSION=x)'
|
54
|
+
task :migrate => :set_keyspace do
|
55
|
+
version = ( ENV['VERSION'] ? ENV['VERSION'].to_i : nil )
|
56
|
+
ActiveColumn::Migrator.migrate ActiveColumn::Migrator.migrations_path, version
|
57
|
+
schema_dump
|
58
|
+
end
|
59
|
+
|
60
|
+
desc 'Rolls the schema back to the previous version (specify steps w/ STEP=n)'
|
61
|
+
task :rollback => :set_keyspace do
|
62
|
+
step = ENV['STEP'] ? ENV['STEP'].to_i : 1
|
63
|
+
ActiveColumn::Migrator.rollback ActiveColumn::Migrator.migrations_path, step
|
64
|
+
schema_dump
|
65
|
+
end
|
66
|
+
|
67
|
+
desc 'Pushes the schema to the next version (specify steps w/ STEP=n)'
|
68
|
+
task :forward => :set_keyspace do
|
69
|
+
step = ENV['STEP'] ? ENV['STEP'].to_i : 1
|
70
|
+
ActiveColumn::Migrator.forward ActiveColumn::Migrator.migrations_path, step
|
71
|
+
schema_dump
|
72
|
+
end
|
73
|
+
|
74
|
+
namespace :schema do
|
75
|
+
desc 'Create ks/schema.json file that can be portably used against any Cassandra instance supported by ActiveColumn'
|
76
|
+
task :dump => :configure do
|
77
|
+
schema_dump
|
78
|
+
end
|
79
|
+
|
80
|
+
desc 'Load ks/schema.json file into Cassandra'
|
81
|
+
task :load => :configure do
|
82
|
+
schema_load
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
namespace :test do
|
87
|
+
desc 'Load the development schema in to the test keyspace'
|
88
|
+
task :prepare => :configure do
|
89
|
+
schema_dump :development
|
90
|
+
schema_load :test
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
desc 'Retrieves the current schema version number'
|
95
|
+
task :version => :set_keyspace do
|
96
|
+
version = ActiveColumn::Migrator.current_version
|
97
|
+
puts "Current version: #{version}"
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def current_root
|
103
|
+
return Rails.root.to_s if defined? ::Rails
|
104
|
+
'.'
|
105
|
+
end
|
106
|
+
|
107
|
+
def configure
|
108
|
+
@configs = YAML.load_file("#{current_root}/config/cassandra.yml")
|
109
|
+
@config = @configs[ActiveColumn::Helpers.current_env]
|
110
|
+
ActiveColumn.connect @config
|
111
|
+
end
|
112
|
+
|
113
|
+
def schema_dump(env = ActiveColumn::Helpers.current_env)
|
114
|
+
ks = set_keyspace env
|
115
|
+
File.open "#{current_root}/ks/schema.json", 'w' do |file|
|
116
|
+
basic_json = ks.schema_dump.to_json
|
117
|
+
formatted_json = JSON.pretty_generate(JSON.parse(basic_json))
|
118
|
+
file.puts formatted_json
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def schema_load(env = ActiveColumn::Helpers.current_env)
|
123
|
+
ks = set_keyspace env
|
124
|
+
File.open "#{current_root}/ks/schema.json", 'r' do |file|
|
125
|
+
hash = JSON.parse(file.read(nil))
|
126
|
+
ks.schema_load ActiveColumn::Tasks::Keyspace.parse(hash)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def set_keyspace(env = ActiveColumn::Helpers.current_env)
|
131
|
+
config = @configs[env.to_s || 'development']
|
132
|
+
ks = ActiveColumn::Tasks::Keyspace.new
|
133
|
+
keyspace = config['keyspace']
|
134
|
+
unless ks.exists? keyspace
|
135
|
+
puts "Keyspace '#{keyspace}' does not exist - Try 'rake ks:create'"
|
136
|
+
exit 1
|
137
|
+
end
|
138
|
+
ks.set keyspace
|
139
|
+
ks
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'mocha'
|
3
|
+
|
4
|
+
describe ActiveColumn::Migration do
|
5
|
+
describe '.create_column_family' do
|
6
|
+
|
7
|
+
context 'given a block' do
|
8
|
+
before do
|
9
|
+
ActiveColumn.connection.expects(:add_column_family).with() do |cf|
|
10
|
+
cf.name == 'foo' && cf.comment = 'some comment'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'sends the settings to cassandra' do
|
15
|
+
ActiveColumn::Migration.create_column_family :foo do |cf|
|
16
|
+
cf.comment = 'some comment'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'given no block' do
|
22
|
+
before do
|
23
|
+
ActiveColumn.connection.expects(:add_column_family).with() do |cf|
|
24
|
+
cf.name == 'foo' && cf.comment.nil?
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'sends the default settings to cassandra' do
|
29
|
+
ActiveColumn::Migration.create_column_family :foo
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '.drop_column_family' do
|
36
|
+
context 'given a column family' do
|
37
|
+
before do
|
38
|
+
ActiveColumn.connection.expects(:drop_column_family).with('foo')
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'drops it' do
|
42
|
+
ActiveColumn::Migration.drop_column_family :foo
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe '.rename_column_family' do
|
48
|
+
context 'given a column family and a new name' do
|
49
|
+
before do
|
50
|
+
ActiveColumn.connection.expects(:rename_column_family).with('old_foo', 'new_foo')
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'renames it' do
|
54
|
+
ActiveColumn::Migration.rename_column_family :old_foo, :new_foo
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -1,8 +1,10 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
def translated_comparator(given)
|
4
|
-
|
5
|
-
cf
|
4
|
+
cf_tasks = ActiveColumn.column_family_tasks
|
5
|
+
cf = Cassandra::ColumnFamily.new
|
6
|
+
cf.comparator_type = given
|
7
|
+
cf_tasks.send(:post_process_column_family, cf).comparator_type
|
6
8
|
end
|
7
9
|
|
8
10
|
describe ActiveColumn::Tasks::ColumnFamily do
|
@@ -3,27 +3,36 @@ require 'spec_helper'
|
|
3
3
|
describe ActiveColumn::Tasks::Keyspace do
|
4
4
|
|
5
5
|
before do
|
6
|
-
@ks = ActiveColumn
|
6
|
+
@ks = ActiveColumn.keyspace_tasks
|
7
7
|
end
|
8
8
|
|
9
|
-
describe "
|
9
|
+
describe "#create" do
|
10
10
|
context "given a keyspace" do
|
11
11
|
before do
|
12
12
|
@ks.drop :ks_create_test if @ks.exists?(:ks_create_test)
|
13
|
-
@ks.create :ks_create_test
|
13
|
+
@keyspace = @ks.create :ks_create_test
|
14
|
+
@no_keyspace = @ks.create :ks_create_test
|
14
15
|
end
|
15
16
|
|
16
17
|
it "creates the keyspace" do
|
17
18
|
@ks.exists?(:ks_create_test).should be
|
18
19
|
end
|
19
20
|
|
21
|
+
it 'returns the keyspace' do
|
22
|
+
@keyspace.should be
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'does not create duplicate keyspaces' do
|
26
|
+
@no_keyspace.should_not be
|
27
|
+
end
|
28
|
+
|
20
29
|
after do
|
21
30
|
@ks.drop :ks_create_test
|
22
31
|
end
|
23
32
|
end
|
24
33
|
end
|
25
34
|
|
26
|
-
describe '
|
35
|
+
describe '#drop' do
|
27
36
|
context 'given a keyspace' do
|
28
37
|
before do
|
29
38
|
@ks.create :ks_drop_test unless @ks.exists?(:ks_drop_test)
|
@@ -33,6 +42,97 @@ describe ActiveColumn::Tasks::Keyspace do
|
|
33
42
|
it 'drops the keyspace' do
|
34
43
|
@ks.exists?(:ks_drop_test).should_not be
|
35
44
|
end
|
45
|
+
|
46
|
+
it 'gracefully does not drop the keyspace again' do
|
47
|
+
@ks.drop :ks_drop_test
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe '.parse' do
|
53
|
+
context 'given a keyspace schema as a hash' do
|
54
|
+
before do
|
55
|
+
@hash = { 'name' => 'ks1',
|
56
|
+
'cf_defs' => [ { 'name' => 'cf1', 'comment' => 'foo' },
|
57
|
+
{ 'name' => 'cf2', 'comment' => 'bar' } ] }
|
58
|
+
@schema = ActiveColumn::Tasks::Keyspace.parse @hash
|
59
|
+
@cfdefs = @schema.cf_defs.sort { |a,b| a.name <=> b.name }
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'returns a keyspace schema' do
|
63
|
+
@schema.should be_a(Cassandra::Keyspace)
|
64
|
+
@schema.name.should == 'ks1'
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'returns all column families' do
|
68
|
+
@cfdefs.collect(&:name).should == [ 'cf1', 'cf2' ]
|
69
|
+
@cfdefs.collect(&:comment).should == [ 'foo', 'bar' ]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe '#schema_dump' do
|
75
|
+
context 'given a keyspace' do
|
76
|
+
before do
|
77
|
+
@ks.drop :ks_schema_dump_test if @ks.exists?(:ks_schema_dump_test)
|
78
|
+
@ks.create :ks_schema_dump_test
|
79
|
+
@ks.set :ks_schema_dump_test
|
80
|
+
cf_tasks = ActiveColumn::Tasks::ColumnFamily.new :ks_schema_dump_test
|
81
|
+
cf_tasks.create(:cf1) { |cf| cf.comment = 'foo' }
|
82
|
+
cf_tasks.create(:cf2) { |cf| cf.comment = 'bar' }
|
83
|
+
@schema = @ks.schema_dump
|
84
|
+
@cfdefs = @schema.cf_defs.sort { |a,b| a.name <=> b.name }
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'dumps the keyspace schema' do
|
88
|
+
@schema.should be
|
89
|
+
@schema.name.should == 'ks_schema_dump_test'
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'dumps all column families' do
|
93
|
+
@cfdefs.collect(&:name).should == [ 'cf1', 'cf2' ]
|
94
|
+
@cfdefs.collect(&:comment).should == [ 'foo', 'bar' ]
|
95
|
+
end
|
96
|
+
|
97
|
+
after do
|
98
|
+
@ks.drop :ks_schema_dump_test
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe '#schema_load' do
|
104
|
+
context 'given a keyspace schema' do
|
105
|
+
before do
|
106
|
+
@ks.drop :ks_schema_load_test if @ks.exists?(:ks_schema_load_test)
|
107
|
+
@ks.create :ks_schema_load_test
|
108
|
+
@ks.set :ks_schema_load_test
|
109
|
+
cf_tasks = ActiveColumn::Tasks::ColumnFamily.new :ks_schema_load_test
|
110
|
+
cf_tasks.create(:cf1) { |cf| cf.comment = 'foo' }
|
111
|
+
cf_tasks.create(:cf2) { |cf| cf.comment = 'bar' }
|
112
|
+
schema = @ks.schema_dump
|
113
|
+
|
114
|
+
@ks.drop :ks_schema_load_test2 if @ks.exists?(:ks_schema_load_test2)
|
115
|
+
@ks.create :ks_schema_load_test2
|
116
|
+
@ks.set :ks_schema_load_test2
|
117
|
+
@ks.schema_load schema
|
118
|
+
@schema2 = @ks.schema_dump
|
119
|
+
@cfdefs2 = @schema2.cf_defs.sort { |a,b| a.name <=> b.name }
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'loads the keyspace' do
|
123
|
+
@schema2.should be
|
124
|
+
@schema2.name.should == 'ks_schema_load_test2'
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'loads all column families' do
|
128
|
+
@cfdefs2.collect(&:name).should == [ 'cf1', 'cf2' ]
|
129
|
+
@cfdefs2.collect(&:comment).should == [ 'foo', 'bar' ]
|
130
|
+
end
|
131
|
+
|
132
|
+
after do
|
133
|
+
@ks.drop :ks_schema_load_test
|
134
|
+
@ks.drop :ks_schema_load_test2
|
135
|
+
end
|
36
136
|
end
|
37
137
|
end
|
38
138
|
end
|