active_column 0.1.1 → 0.2
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/.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
|