cassandra_migrations 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/cassandra_migrations/cassandra/queries.rb +46 -6
- data/lib/cassandra_migrations/migration/table_definition.rb +86 -26
- data/lib/cassandra_migrations/migration/table_operations.rb +21 -0
- data/spec/cassandra_migrations/cassandra/queries_spec.rb +146 -0
- data/spec/cassandra_migrations_spec.rb +154 -0
- metadata +1 -1
@@ -10,7 +10,7 @@ module CassandraMigrations
|
|
10
10
|
|
11
11
|
value_hash.each do |column, value|
|
12
12
|
columns << column.to_s
|
13
|
-
values << to_cql_value(value)
|
13
|
+
values << to_cql_value(column, value, table, options)
|
14
14
|
end
|
15
15
|
|
16
16
|
query = "INSERT INTO #{table} (#{columns.join(', ')}) VALUES (#{values.join(', ')})"
|
@@ -25,7 +25,7 @@ module CassandraMigrations
|
|
25
25
|
def update!(table, selection, value_hash, options={})
|
26
26
|
set_terms = []
|
27
27
|
value_hash.each do |column, value|
|
28
|
-
set_terms << "#{column} = #{to_cql_value(value)}"
|
28
|
+
set_terms << "#{column} = #{to_cql_value(column, value, table, options)}"
|
29
29
|
end
|
30
30
|
|
31
31
|
query = "UPDATE #{table}"
|
@@ -53,6 +53,10 @@ module CassandraMigrations
|
|
53
53
|
if options[:limit]
|
54
54
|
query_string << " LIMIT #{options[:limit]}"
|
55
55
|
end
|
56
|
+
|
57
|
+
if options[:allow_filtering]
|
58
|
+
query_string << " ALLOW FILTERING"
|
59
|
+
end
|
56
60
|
|
57
61
|
execute(query_string)
|
58
62
|
end
|
@@ -68,13 +72,49 @@ module CassandraMigrations
|
|
68
72
|
|
69
73
|
private
|
70
74
|
|
71
|
-
def
|
75
|
+
def get_column_type(table, column)
|
76
|
+
column_info = client.execute("SELECT VALIDATOR FROM system.schema_columns WHERE keyspace_name = '#{client.keyspace}' AND columnfamily_name = '#{table}' AND column_name = '#{column}'")
|
77
|
+
raw_type = column_info.first['validator']
|
78
|
+
|
79
|
+
case
|
80
|
+
when raw_type.include?('SetType'); :set
|
81
|
+
when raw_type.include?('ListType'); :list
|
82
|
+
when raw_type.include?('MapType'); :map
|
83
|
+
when raw_type.include?('BooleanType'); :boolean
|
84
|
+
when raw_type.include?('FloatType'); :float
|
85
|
+
when raw_type.include?('Int32Type'); :int
|
86
|
+
when raw_type.include?('DateType'); :timestamp
|
87
|
+
when raw_type.include?('UTF8Type'); :string
|
88
|
+
when raw_type.include?('BytesType'); :blob
|
89
|
+
when raw_type.include?('UUIDType'); :uuid
|
90
|
+
when raw_type.include?('DoubleType'); :double
|
91
|
+
when raw_type.include?('InetAddressType'); :inet
|
92
|
+
when raw_type.include?('AsciiType'); :ascii
|
93
|
+
when raw_type.include?('LongType'); :bigint
|
94
|
+
when raw_type.include?('DecimalType'); :decimal
|
95
|
+
when raw_type.include?('TimeUUIDType'); :timeuuid
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def to_cql_value(column, value, table, options={})
|
100
|
+
operator = options[:operations] ? options[:operations][column.to_sym] : nil
|
101
|
+
operation = operator ? "#{column} #{operator} " : ''
|
102
|
+
|
72
103
|
if value.respond_to?(:strftime)
|
73
104
|
"'#{value.strftime('%Y-%m-%d %H:%M:%S%z')}'"
|
74
105
|
elsif value.is_a?(String)
|
75
|
-
"'#{value}'"
|
76
|
-
|
77
|
-
|
106
|
+
"'#{value}'"
|
107
|
+
elsif value.is_a?(Array)
|
108
|
+
type = get_column_type(table, column)
|
109
|
+
values = %[#{value.map {|v| "'#{v}'"} * ', '}]
|
110
|
+
|
111
|
+
if type && type == :list
|
112
|
+
%[#{operation}[#{values}]]
|
113
|
+
else # it must be a set!
|
114
|
+
%[#{operation}{#{values}}]
|
115
|
+
end
|
116
|
+
elsif value.is_a?(Hash)
|
117
|
+
"#{operation}{ #{value.reduce([]) {|sum, (key, value)| sum << "'#{key}': '#{value}'" }.join(", ") } }"
|
78
118
|
else
|
79
119
|
value.to_s
|
80
120
|
end
|
@@ -26,13 +26,13 @@ module CassandraMigrations
|
|
26
26
|
cql << "#{column_name} #{type}"
|
27
27
|
end
|
28
28
|
else
|
29
|
-
raise Errors::MigrationDefinitionError
|
29
|
+
raise Errors::MigrationDefinitionError, 'No columns defined for table.'
|
30
30
|
end
|
31
31
|
|
32
32
|
if !@primary_keys.empty?
|
33
33
|
cql << "PRIMARY KEY(#{@primary_keys.join(', ')})"
|
34
34
|
else
|
35
|
-
raise Errors::MigrationDefinitionError
|
35
|
+
raise Errors::MigrationDefinitionError, 'No primary key defined.'
|
36
36
|
end
|
37
37
|
|
38
38
|
cql.join(', ')
|
@@ -44,78 +44,138 @@ module CassandraMigrations
|
|
44
44
|
if @columns_name_type_hash.size == 1
|
45
45
|
cql = "#{@columns_name_type_hash.keys.first} #{@columns_name_type_hash.values.first}"
|
46
46
|
elsif @columns_name_type_hash.empty?
|
47
|
-
raise Errors::MigrationDefinitionError
|
47
|
+
raise Errors::MigrationDefinitionError, 'No column to add.'
|
48
48
|
else
|
49
|
-
raise Errors::MigrationDefinitionError
|
49
|
+
raise Errors::MigrationDefinitionError, 'Only one column can be added at once.'
|
50
50
|
end
|
51
51
|
|
52
52
|
cql
|
53
53
|
end
|
54
54
|
|
55
55
|
def boolean(column_name, options={})
|
56
|
-
@columns_name_type_hash[column_name.to_sym] = :boolean
|
56
|
+
@columns_name_type_hash[column_name.to_sym] = column_type_for(:boolean, options)
|
57
57
|
define_primary_keys(column_name) if options[:primary_key]
|
58
58
|
end
|
59
59
|
|
60
60
|
def integer(column_name, options={})
|
61
|
-
|
62
|
-
@columns_name_type_hash[column_name.to_sym] = :int
|
63
|
-
elsif options[:limit] == 8
|
64
|
-
@columns_name_type_hash[column_name.to_sym] = :bigint
|
65
|
-
else
|
66
|
-
raise Errors::MigrationDefinitionError(':limit option should be 4 or 8 for integers.')
|
67
|
-
end
|
61
|
+
@columns_name_type_hash[column_name.to_sym] = column_type_for(:integer, options)
|
68
62
|
define_primary_keys(column_name) if options[:primary_key]
|
69
63
|
end
|
70
64
|
|
71
65
|
def float(column_name, options={})
|
72
|
-
|
73
|
-
@columns_name_type_hash[column_name.to_sym] = :float
|
74
|
-
elsif options[:limit] == 8
|
75
|
-
@columns_name_type_hash[column_name.to_sym] = :double
|
76
|
-
else
|
77
|
-
raise Errors::MigrationDefinitionError(':limit option should be 4 or 8 for floats.')
|
78
|
-
end
|
66
|
+
@columns_name_type_hash[column_name.to_sym] = column_type_for(:float, options)
|
79
67
|
define_primary_keys(column_name) if options[:primary_key]
|
80
68
|
end
|
81
69
|
|
82
70
|
def string(column_name, options={})
|
83
|
-
@columns_name_type_hash[column_name.to_sym] = :
|
71
|
+
@columns_name_type_hash[column_name.to_sym] = column_type_for(:string, options)
|
84
72
|
define_primary_keys(column_name) if options[:primary_key]
|
85
73
|
end
|
86
74
|
|
87
75
|
def text(column_name, options={})
|
88
|
-
@columns_name_type_hash[column_name.to_sym] = :text
|
76
|
+
@columns_name_type_hash[column_name.to_sym] = column_type_for(:text, options)
|
89
77
|
define_primary_keys(column_name) if options[:primary_key]
|
90
78
|
end
|
91
79
|
|
92
80
|
def datetime(column_name, options={})
|
93
|
-
@columns_name_type_hash[column_name.to_sym] = :
|
81
|
+
@columns_name_type_hash[column_name.to_sym] = column_type_for(:datetime, options)
|
94
82
|
define_primary_keys(column_name) if options[:primary_key]
|
95
83
|
end
|
96
84
|
|
97
85
|
def timestamp(column_name, options={})
|
98
|
-
@columns_name_type_hash[column_name.to_sym] = :timestamp
|
86
|
+
@columns_name_type_hash[column_name.to_sym] = column_type_for(:timestamp, options)
|
99
87
|
define_primary_keys(column_name) if options[:primary_key]
|
100
88
|
end
|
101
89
|
|
102
90
|
def uuid(column_name, options={})
|
103
|
-
@columns_name_type_hash[column_name.to_sym] = :uuid
|
91
|
+
@columns_name_type_hash[column_name.to_sym] = column_type_for(:uuid, options)
|
104
92
|
define_primary_keys(column_name) if options[:primary_key]
|
105
93
|
end
|
106
94
|
|
107
95
|
def timeuuid(column_name, options={})
|
108
|
-
@columns_name_type_hash[column_name.to_sym] = :timeuuid
|
96
|
+
@columns_name_type_hash[column_name.to_sym] = column_type_for(:timeuuid, options)
|
109
97
|
define_primary_keys(column_name) if options[:primary_key]
|
110
98
|
end
|
111
99
|
|
100
|
+
def list(column_name, options={})
|
101
|
+
type = options[:type]
|
102
|
+
if type.nil?
|
103
|
+
raise Errors::MigrationDefinitionError, 'A list must define a collection type.'
|
104
|
+
elsif !self.respond_to?(type)
|
105
|
+
raise Errors::MigrationDefinitionError, "Type '#{type}' is not valid for cassandra migration."
|
106
|
+
end
|
107
|
+
if options[:primary_key]
|
108
|
+
raise Errors::MigrationDefinitionError, 'A collection cannot be used as a primary key.'
|
109
|
+
end
|
110
|
+
@columns_name_type_hash[column_name.to_sym] = :"list<#{column_type_for(type)}>"
|
111
|
+
end
|
112
|
+
|
113
|
+
def set(column_name, options={})
|
114
|
+
type = options[:type]
|
115
|
+
if type.nil?
|
116
|
+
raise Errors::MigrationDefinitionError, 'A set must define a collection type.'
|
117
|
+
elsif !self.respond_to?(type)
|
118
|
+
raise Errors::MigrationDefinitionError, "Type '#{type}' is not valid for cassandra migration."
|
119
|
+
end
|
120
|
+
if options[:primary_key]
|
121
|
+
raise Errors::MigrationDefinitionError, 'A collection cannot be used as a primary key.'
|
122
|
+
end
|
123
|
+
@columns_name_type_hash[column_name.to_sym] = :"set<#{column_type_for(type)}>"
|
124
|
+
end
|
125
|
+
|
126
|
+
def map(column_name, options={})
|
127
|
+
key_type, value_type = options[:key_type], options[:value_type]
|
128
|
+
[key_type, value_type].each_with_index do |type, index|
|
129
|
+
if type.nil?
|
130
|
+
raise Errors::MigrationDefinitionError, "A map must define a #{index = 0 ? 'key' : 'value'} type."
|
131
|
+
elsif !self.respond_to?(type)
|
132
|
+
raise Errors::MigrationDefinitionError, "Type '#{type}' is not valid for cassandra migration."
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
if options[:primary_key]
|
137
|
+
raise Errors::MigrationDefinitionError, 'A collection cannot be used as a primary key.'
|
138
|
+
end
|
139
|
+
@columns_name_type_hash[column_name.to_sym] = :"map<#{column_type_for(key_type)},#{column_type_for(value_type)}>"
|
140
|
+
end
|
141
|
+
|
112
142
|
def define_primary_keys(*keys)
|
113
143
|
if !@primary_keys.empty?
|
114
|
-
raise Errors::MigrationDefinitionError
|
144
|
+
raise Errors::MigrationDefinitionError, 'Primary key defined twice for the same table.'
|
115
145
|
end
|
116
146
|
|
117
147
|
@primary_keys = keys.flatten
|
118
148
|
end
|
149
|
+
|
150
|
+
private
|
151
|
+
|
152
|
+
def column_type_for(ruby_type, options={})
|
153
|
+
limit = options[:limit]
|
154
|
+
case ruby_type
|
155
|
+
when :boolean, :text, :timestamp, :uuid, :timeuuid
|
156
|
+
ruby_type
|
157
|
+
when :integer
|
158
|
+
if limit.nil? || limit == 4
|
159
|
+
:int
|
160
|
+
elsif limit == 8
|
161
|
+
:bigint
|
162
|
+
else
|
163
|
+
raise Errors::MigrationDefinitionError, ':limit option should be 4 or 8 for integers.'
|
164
|
+
end
|
165
|
+
when :float
|
166
|
+
if limit.nil? || limit == 4
|
167
|
+
:float
|
168
|
+
elsif limit == 8
|
169
|
+
:double
|
170
|
+
else
|
171
|
+
raise Errors::MigrationDefinitionError, ':limit option should be 4 or 8 for floats.'
|
172
|
+
end
|
173
|
+
when :string
|
174
|
+
:varchar
|
175
|
+
when :datetime
|
176
|
+
:timestamp
|
177
|
+
end
|
178
|
+
end
|
119
179
|
end
|
120
180
|
end
|
121
181
|
end
|
@@ -33,6 +33,14 @@ module CassandraMigrations
|
|
33
33
|
execute create_cql
|
34
34
|
end
|
35
35
|
|
36
|
+
def create_index(table_name, column_name, options = {})
|
37
|
+
announce_operation "create_index(#{table_name})"
|
38
|
+
create_index_cql = "CREATE INDEX #{options[:name]} ON #{table_name} (#{column_name})".squeeze(' ')
|
39
|
+
announce_suboperation create_index_cql
|
40
|
+
|
41
|
+
execute create_index_cql
|
42
|
+
end
|
43
|
+
|
36
44
|
# Drops a table
|
37
45
|
def drop_table(table_name)
|
38
46
|
announce_operation "drop_table(#{table_name})"
|
@@ -41,6 +49,19 @@ module CassandraMigrations
|
|
41
49
|
|
42
50
|
execute drop_cql
|
43
51
|
end
|
52
|
+
|
53
|
+
def drop_index(table_or_index_name, column_name = nil, options = {})
|
54
|
+
if column_name
|
55
|
+
index_name = "#{table_or_index_name}_#{column_name}_idx"
|
56
|
+
else
|
57
|
+
index_name = table_or_index_name
|
58
|
+
end
|
59
|
+
drop_index_cql = "DROP INDEX #{options[:if_exists] ? 'IF EXISTS' : ''}#{index_name}"
|
60
|
+
announce_suboperation drop_index_cql
|
61
|
+
|
62
|
+
execute drop_index_cql
|
63
|
+
end
|
64
|
+
|
44
65
|
end
|
45
66
|
end
|
46
67
|
end
|
@@ -5,6 +5,16 @@ describe CassandraMigrations::Cassandra::Queries do
|
|
5
5
|
|
6
6
|
class TestQueryExecutor
|
7
7
|
extend CassandraMigrations::Cassandra::Queries
|
8
|
+
|
9
|
+
def self.column_type=(column_type)
|
10
|
+
@column_type = column_type
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def self.get_column_type(table, column)
|
16
|
+
@column_type
|
17
|
+
end
|
8
18
|
end
|
9
19
|
|
10
20
|
describe '.write!' do
|
@@ -28,6 +38,39 @@ describe CassandraMigrations::Cassandra::Queries do
|
|
28
38
|
|
29
39
|
TestQueryExecutor.write!('people', {:name => 'John'}, :ttl => 3600)
|
30
40
|
end
|
41
|
+
|
42
|
+
context 'when dealing with collections' do
|
43
|
+
|
44
|
+
it 'should handle setting a set collection column value' do
|
45
|
+
TestQueryExecutor.column_type = :set
|
46
|
+
|
47
|
+
TestQueryExecutor.should_receive(:execute).with(
|
48
|
+
"INSERT INTO people (friends) VALUES ({'John', 'Ringo', 'Paul', 'George'})"
|
49
|
+
)
|
50
|
+
|
51
|
+
TestQueryExecutor.write!('people', {friends: ['John', 'Ringo', 'Paul', 'George']})
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should handle setting a list collection column value' do
|
55
|
+
TestQueryExecutor.column_type = :list
|
56
|
+
|
57
|
+
TestQueryExecutor.should_receive(:execute).with(
|
58
|
+
"INSERT INTO people (friends) VALUES (['John', 'Ringo', 'Paul', 'George'])"
|
59
|
+
)
|
60
|
+
|
61
|
+
TestQueryExecutor.write!('people', {friends: ['John', 'Ringo', 'Paul', 'George']})
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'should handle setting a map collection column value' do
|
65
|
+
TestQueryExecutor.column_type = :map
|
66
|
+
|
67
|
+
TestQueryExecutor.should_receive(:execute).with(
|
68
|
+
"INSERT INTO people (friends) VALUES ({ 'talent': 'John', 'drums': 'Ringo', 'voice': 'Paul', 'rhythm': 'George' })"
|
69
|
+
)
|
70
|
+
|
71
|
+
TestQueryExecutor.write!('people', {friends: {talent: 'John', drums: 'Ringo', voice: 'Paul', rhythm: 'George'}})
|
72
|
+
end
|
73
|
+
end
|
31
74
|
end
|
32
75
|
|
33
76
|
describe '.update!' do
|
@@ -49,6 +92,109 @@ describe CassandraMigrations::Cassandra::Queries do
|
|
49
92
|
|
50
93
|
TestQueryExecutor.update!('people', "name = 'John'", {:name => 'Johnny'}, :ttl => 3600)
|
51
94
|
end
|
95
|
+
|
96
|
+
context 'when dealing with collections' do
|
97
|
+
|
98
|
+
it 'should handle setting a set collection column' do
|
99
|
+
TestQueryExecutor.column_type = :set
|
100
|
+
|
101
|
+
TestQueryExecutor.should_receive(:execute).with(
|
102
|
+
"UPDATE people SET friends = {'John', 'Ringo', 'Paul', 'George'} WHERE name = 'Stuart'"
|
103
|
+
)
|
104
|
+
|
105
|
+
TestQueryExecutor.update!('people', "name = 'Stuart'", {friends: ['John', 'Ringo', 'Paul', 'George']})
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'should handle adding elements to a set collection column' do
|
109
|
+
TestQueryExecutor.column_type = :set
|
110
|
+
|
111
|
+
TestQueryExecutor.should_receive(:execute).with(
|
112
|
+
"UPDATE people SET friends = friends + {'John', 'Ringo', 'Paul', 'George'} WHERE name = 'Stuart'"
|
113
|
+
)
|
114
|
+
|
115
|
+
TestQueryExecutor.update!('people', "name = 'Stuart'",
|
116
|
+
{friends: ['John', 'Ringo', 'Paul', 'George']},
|
117
|
+
{operations: {friends: :+}})
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'should handle removing elements from a set collection column' do
|
121
|
+
TestQueryExecutor.column_type = :set
|
122
|
+
|
123
|
+
TestQueryExecutor.should_receive(:execute).with(
|
124
|
+
"UPDATE people SET friends = friends - {'John', 'Ringo', 'Paul', 'George'} WHERE name = 'Stuart'"
|
125
|
+
)
|
126
|
+
|
127
|
+
TestQueryExecutor.update!('people', "name = 'Stuart'",
|
128
|
+
{friends: ['John', 'Ringo', 'Paul', 'George']},
|
129
|
+
{operations: {friends: :-}})
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'should handle setting a list collection column' do
|
133
|
+
TestQueryExecutor.column_type = :list
|
134
|
+
|
135
|
+
TestQueryExecutor.should_receive(:execute).with(
|
136
|
+
"UPDATE people SET friends = ['John', 'Ringo', 'Paul', 'George'] WHERE name = 'Stuart'"
|
137
|
+
)
|
138
|
+
|
139
|
+
TestQueryExecutor.update!('people', "name = 'Stuart'", {friends: ['John', 'Ringo', 'Paul', 'George']})
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'should handle adding elements to a list collection column' do
|
143
|
+
TestQueryExecutor.column_type = :list
|
144
|
+
|
145
|
+
TestQueryExecutor.should_receive(:execute).with(
|
146
|
+
"UPDATE people SET friends = friends + ['John', 'Ringo', 'Paul', 'George'] WHERE name = 'Stuart'"
|
147
|
+
)
|
148
|
+
|
149
|
+
TestQueryExecutor.update!('people', "name = 'Stuart'",
|
150
|
+
{friends: ['John', 'Ringo', 'Paul', 'George']},
|
151
|
+
{operations: {friends: :+}})
|
152
|
+
end
|
153
|
+
|
154
|
+
it 'should handle removing elements from a list collection column' do
|
155
|
+
TestQueryExecutor.column_type = :list
|
156
|
+
|
157
|
+
TestQueryExecutor.should_receive(:execute).with(
|
158
|
+
"UPDATE people SET friends = friends - ['John', 'Ringo', 'Paul', 'George'] WHERE name = 'Stuart'"
|
159
|
+
)
|
160
|
+
|
161
|
+
TestQueryExecutor.update!('people', "name = 'Stuart'",
|
162
|
+
{friends: ['John', 'Ringo', 'Paul', 'George']},
|
163
|
+
{operations: {friends: :-}})
|
164
|
+
end
|
165
|
+
|
166
|
+
it 'should handle setting a map collection column' do
|
167
|
+
TestQueryExecutor.column_type = :map
|
168
|
+
|
169
|
+
TestQueryExecutor.should_receive(:execute).with(
|
170
|
+
"UPDATE people SET friends = { 'talent': 'John', 'drums': 'Ringo', 'voice': 'Paul', 'rhythm': 'George' } WHERE name = 'Stuart'"
|
171
|
+
)
|
172
|
+
|
173
|
+
TestQueryExecutor.update!('people', "name = 'Stuart'", {friends: {talent: 'John', drums: 'Ringo', voice: 'Paul', rhythm: 'George'}})
|
174
|
+
end
|
175
|
+
|
176
|
+
it 'should handle adding elements to a map collection column' do
|
177
|
+
TestQueryExecutor.column_type = :map
|
178
|
+
|
179
|
+
TestQueryExecutor.should_receive(:execute).with(
|
180
|
+
"UPDATE people SET friends = friends + { 'talent': 'John', 'drums': 'Ringo', 'voice': 'Paul', 'rhythm': 'George' } WHERE name = 'Stuart'"
|
181
|
+
)
|
182
|
+
|
183
|
+
TestQueryExecutor.update!('people', "name = 'Stuart'",
|
184
|
+
{friends: {talent: 'John', drums: 'Ringo', voice: 'Paul', rhythm: 'George'}},
|
185
|
+
{operations: {friends: :+}})
|
186
|
+
end
|
187
|
+
|
188
|
+
it 'should handle removing elements from a map collection column' do
|
189
|
+
TestQueryExecutor.column_type = :map
|
190
|
+
|
191
|
+
TestQueryExecutor.should_receive(:execute).with(
|
192
|
+
"DELETE friends['drums'] FROM people WHERE name = 'Stuart'"
|
193
|
+
)
|
194
|
+
|
195
|
+
TestQueryExecutor.delete!('people', "name = 'Stuart'", :projection => "friends['drums']")
|
196
|
+
end
|
197
|
+
end
|
52
198
|
end
|
53
199
|
|
54
200
|
describe '.select' do
|
@@ -1,7 +1,26 @@
|
|
1
1
|
# encoding : utf-8
|
2
2
|
require 'spec_helper'
|
3
3
|
|
4
|
+
module CassandraMigrations
|
5
|
+
|
6
|
+
# Temporarily monkey path Migration to allow for testing generated CQL
|
7
|
+
# and suppress announcements
|
8
|
+
class Migration
|
9
|
+
attr_reader :cql
|
10
|
+
|
11
|
+
def execute(cql)
|
12
|
+
@cql = cql
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
def announce_migration(message); end
|
17
|
+
def announce_operation(message); end
|
18
|
+
def announce_suboperation(message); end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
4
22
|
describe CassandraMigrations do
|
23
|
+
|
5
24
|
it "should define modules" do
|
6
25
|
defined?(CassandraMigrations).should be_true
|
7
26
|
defined?(CassandraMigrations::Railtie).should be_true
|
@@ -11,4 +30,139 @@ describe CassandraMigrations do
|
|
11
30
|
defined?(CassandraMigrations::Migrator).should be_true
|
12
31
|
defined?(CassandraMigrations::Config).should be_true
|
13
32
|
end
|
33
|
+
|
34
|
+
context 'a migration' do
|
35
|
+
before do
|
36
|
+
require_relative 'fixtures/migrations/migrations'
|
37
|
+
end
|
38
|
+
|
39
|
+
context 'without a primary key' do
|
40
|
+
before do
|
41
|
+
@migration = WithoutAPrimaryKey.new
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'should be invalid' do
|
45
|
+
expect { @migration.up }.to raise_error(CassandraMigrations::Errors::MigrationDefinitionError, /No primary key defined./)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'that is valid' do
|
50
|
+
before do
|
51
|
+
@migration = CreateKitchenSink.new
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should have a name' do
|
55
|
+
expect(@migration.send(:name)).to eq('CreateKitchenSink')
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'should produce a valid CQL create statement' do
|
59
|
+
@migration.up
|
60
|
+
expected_cql = "CREATE TABLE kitchen_sink (id uuid, a_string varchar, a_timestamp timestamp, a_float float, a_list_of_strings list<varchar>, PRIMARY KEY(id))"
|
61
|
+
expect(@migration.cql).to eq(expected_cql)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'with a list collection column declaration' do
|
66
|
+
before do
|
67
|
+
@migration = CollectionsListMigration.new
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'should produce a valid CQL create statement' do
|
71
|
+
@migration.up
|
72
|
+
expected_cql = "CREATE TABLE collection_lists (id uuid, list_1 list<varchar>, PRIMARY KEY(id))"
|
73
|
+
expect(@migration.cql).to eq(expected_cql)
|
74
|
+
end
|
75
|
+
|
76
|
+
context 'using invalid types' do
|
77
|
+
before do
|
78
|
+
@migration = BadCollectionsListMigration.new
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'should be invalid' do
|
82
|
+
expect { @migration.up }.to raise_error(CassandraMigrations::Errors::MigrationDefinitionError, /Type 'chupacabra' is not valid for cassandra migration./)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context 'with a set collection column declaration' do
|
88
|
+
before do
|
89
|
+
@migration = CollectionsSetMigration.new
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'should produce a valid CQL create statement' do
|
93
|
+
@migration.up
|
94
|
+
expected_cql = "CREATE TABLE collection_lists (id uuid, set_2 set<float>, PRIMARY KEY(id))"
|
95
|
+
expect(@migration.cql).to eq(expected_cql)
|
96
|
+
end
|
97
|
+
|
98
|
+
context 'using invalid types' do
|
99
|
+
before do
|
100
|
+
@migration = BadCollectionsSetMigration.new
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'should be invalid' do
|
104
|
+
expect { @migration.up }.to raise_error(CassandraMigrations::Errors::MigrationDefinitionError, /Type 'narwhal' is not valid for cassandra migration./)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
context 'with a map collection column declaration' do
|
110
|
+
before do
|
111
|
+
@migration = CollectionsMapMigration.new
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'should produce a valid CQL create statement' do
|
115
|
+
@migration.up
|
116
|
+
expected_cql = "CREATE TABLE collection_lists (id uuid, map_1 map<varchar,float>, PRIMARY KEY(id))"
|
117
|
+
expect(@migration.cql).to eq(expected_cql)
|
118
|
+
end
|
119
|
+
|
120
|
+
context 'using invalid types' do
|
121
|
+
before do
|
122
|
+
@migration = BadCollectionsMapMigration.new
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'should be invalid' do
|
126
|
+
expect { @migration.up }.to raise_error(CassandraMigrations::Errors::MigrationDefinitionError, /Type 'unicorns' is not valid for cassandra migration./)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
context 'with an secondary index definition' do
|
132
|
+
before do
|
133
|
+
@migration = MigrationWithSecondaryIndex.new
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'should produce a valid CQL create statement' do
|
137
|
+
@migration.up
|
138
|
+
expected_cql = "CREATE INDEX ON with_indexes (a_string)"
|
139
|
+
expect(@migration.cql).to eq(expected_cql)
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'should produce a valid CQL drop statement' do
|
143
|
+
@migration.down
|
144
|
+
expected_cql = "DROP INDEX with_indexes_a_string_idx"
|
145
|
+
expect(@migration.cql).to eq(expected_cql)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
context 'with a named secondary index definition' do
|
150
|
+
before do
|
151
|
+
@migration = MigrationWithANamedSecondaryIndex.new
|
152
|
+
end
|
153
|
+
|
154
|
+
it 'should produce a valid CQL create statement' do
|
155
|
+
@migration.up
|
156
|
+
expected_cql = "CREATE INDEX by_another_string ON with_indexes (another_string)"
|
157
|
+
expect(@migration.cql).to eq(expected_cql)
|
158
|
+
end
|
159
|
+
|
160
|
+
it 'should produce a valid CQL drop statement' do
|
161
|
+
@migration.down
|
162
|
+
expected_cql = "DROP INDEX by_another_string"
|
163
|
+
expect(@migration.cql).to eq(expected_cql)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
14
168
|
end
|