cassandra_migrations 0.0.4 → 0.0.5
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/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
|