cassie 1.0.0.beta.33 → 1.0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/cassie +8 -181
- data/lib/cassie/configuration/core.rb +26 -3
- data/lib/cassie/configuration/generator.rb +1 -0
- data/lib/cassie/configuration/loading.rb +5 -2
- data/lib/cassie/configuration.rb +1 -0
- data/lib/cassie/connection.rb +13 -7
- data/lib/cassie/connection_handler/README.md +13 -3
- data/lib/cassie/connection_handler/cluster.rb +11 -0
- data/lib/cassie/connection_handler/sessions.rb +9 -0
- data/lib/cassie/connection_handler.rb +8 -7
- data/lib/cassie/definition.rb +28 -0
- data/lib/cassie/extensions/object/color_methods.rb +21 -0
- data/lib/cassie/instrumentation.rb +4 -0
- data/lib/cassie/modification.rb +29 -0
- data/lib/cassie/query.rb +27 -0
- data/lib/cassie/schema/README.md +306 -0
- data/lib/cassie/schema/apply_command.rb +24 -0
- data/lib/cassie/schema/cassandra_migrations/importer.rb +91 -0
- data/lib/cassie/schema/cassandra_migrations/migration_file.rb +51 -0
- data/lib/cassie/schema/configuration.rb +35 -0
- data/lib/cassie/schema/migration/cassandra_support.rb +34 -0
- data/lib/cassie/schema/migration/dsl/announcing.rb +47 -0
- data/lib/cassie/schema/migration/dsl/column_operations.rb +42 -0
- data/lib/cassie/schema/migration/dsl/table_definition.rb +299 -0
- data/lib/cassie/schema/migration/dsl/table_operations.rb +64 -0
- data/lib/cassie/schema/migration/dsl.rb +17 -0
- data/lib/cassie/schema/migration.rb +12 -0
- data/lib/cassie/schema/migrator.rb +115 -0
- data/lib/cassie/schema/queries/create_keyspace_query.rb +26 -0
- data/lib/cassie/{migration → schema}/queries/create_versions_table_query.rb +6 -6
- data/lib/cassie/schema/queries/delete_version_query.rb +17 -0
- data/lib/cassie/schema/queries/drop_keyspace_query.rb +14 -0
- data/lib/cassie/schema/queries/insert_version_query.rb +22 -0
- data/lib/cassie/schema/queries/select_versions_query.rb +18 -0
- data/lib/cassie/{migration → schema}/queries.rb +4 -2
- data/lib/cassie/schema/rollback_command.rb +24 -0
- data/lib/cassie/schema/structure_dumper.rb +117 -0
- data/lib/cassie/{migration → schema}/structure_loader.rb +3 -3
- data/lib/cassie/schema/version.rb +143 -0
- data/lib/cassie/schema/version_file_loader.rb +34 -0
- data/lib/cassie/schema/version_loader.rb +31 -0
- data/lib/cassie/schema/version_object_loader.rb +19 -0
- data/lib/cassie/schema/version_writer.rb +108 -0
- data/lib/cassie/schema/versioning.rb +162 -0
- data/lib/cassie/schema.rb +24 -0
- data/lib/cassie/statements/README.md +61 -9
- data/lib/cassie/statements/core.rb +16 -5
- data/lib/cassie/statements/execution/results/core.rb +1 -1
- data/lib/cassie/statements/execution/results/modification_result.rb +1 -1
- data/lib/cassie/statements/execution/results/query_result.rb +1 -1
- data/lib/cassie/statements/execution.rb +40 -13
- data/lib/cassie/statements/statement/assignments.rb +33 -3
- data/lib/cassie/statements/statement/conditions.rb +3 -1
- data/lib/cassie/statements/statement/deleting.rb +27 -19
- data/lib/cassie/statements/statement/idempotency.rb +23 -4
- data/lib/cassie/statements/statement/inserting.rb +17 -10
- data/lib/cassie/statements/statement/limiting.rb +5 -2
- data/lib/cassie/statements/statement/mapping.rb +34 -6
- data/lib/cassie/statements/statement/preparation/cache.rb +1 -1
- data/lib/cassie/statements/statement/preparation.rb +37 -7
- data/lib/cassie/statements/statement/relations.rb +29 -8
- data/lib/cassie/statements/statement/selection.rb +51 -15
- data/lib/cassie/statements/statement/type_hinting.rb +12 -4
- data/lib/cassie/statements/statement/updating.rb +22 -8
- data/lib/cassie/statements/statement.rb +39 -14
- data/lib/cassie/statements.rb +12 -0
- data/lib/cassie/support/server_process.rb +117 -0
- data/lib/cassie/support/statement_parser.rb +3 -5
- data/lib/cassie/support/{command_runner.rb → system_command.rb} +22 -13
- data/lib/cassie/support.rb +3 -1
- data/lib/cassie/tasks/configuration/generate.rake +35 -0
- data/lib/cassie/tasks/io.rb +15 -0
- data/lib/cassie/tasks/migration/create.rake +49 -0
- data/lib/cassie/tasks/migration/import.rake +39 -0
- data/lib/cassie/tasks/migration/reset.rake +9 -0
- data/lib/cassie/tasks/restart.rake +5 -0
- data/lib/cassie/tasks/schema/drop.rake +28 -0
- data/lib/cassie/tasks/schema/dump.rake +21 -0
- data/lib/cassie/tasks/schema/history.rake +18 -0
- data/lib/cassie/tasks/schema/import.rake +40 -0
- data/lib/cassie/tasks/schema/init.rake +54 -0
- data/lib/cassie/tasks/schema/load.rake +19 -0
- data/lib/cassie/tasks/schema/migrate.rake +42 -0
- data/lib/cassie/tasks/schema/reset.rake +6 -0
- data/lib/cassie/tasks/schema/status.rake +19 -0
- data/lib/cassie/tasks/schema/version.rake +18 -0
- data/lib/cassie/tasks/schema/version_display.rb +50 -0
- data/lib/cassie/tasks/start.rake +17 -0
- data/lib/cassie/tasks/stop.rake +33 -0
- data/lib/cassie/tasks/tail.rake +14 -0
- data/lib/cassie/tasks/task_runner.rb +49 -0
- data/lib/cassie/tasks.rb +18 -0
- data/lib/cassie/testing/fake/execution_info.rb +1 -1
- data/lib/cassie/testing/fake/result.rb +3 -3
- data/lib/cassie/testing.rb +4 -0
- data/lib/cassie/version.rb +1 -1
- data/lib/cassie.rb +4 -1
- metadata +73 -17
- data/lib/cassie/migration/README.md +0 -141
- data/lib/cassie/migration/configuration.rb +0 -18
- data/lib/cassie/migration/initialization.rb +0 -70
- data/lib/cassie/migration/queries/create_schema_keyspace_query.rb +0 -17
- data/lib/cassie/migration/queries/insert_version_query.rb +0 -23
- data/lib/cassie/migration/queries/select_versions_query.rb +0 -14
- data/lib/cassie/migration/structure_dumper.rb +0 -94
- data/lib/cassie/migration/version.rb +0 -4
- data/lib/cassie/migration.rb +0 -30
@@ -0,0 +1,299 @@
|
|
1
|
+
module Cassie::Schema::Migration::DSL
|
2
|
+
class Errors
|
3
|
+
class MigrationDefinitionError < StandardError; end
|
4
|
+
end
|
5
|
+
|
6
|
+
# Used to define a table in a migration of table creation or to
|
7
|
+
# add columns to an existing table.
|
8
|
+
#
|
9
|
+
# An instance of this class is passed to the block of the method
|
10
|
+
# +create_table+, available on every migration.
|
11
|
+
#
|
12
|
+
# This class is also internally used in the method +add_column+.
|
13
|
+
class TableDefinition
|
14
|
+
|
15
|
+
#
|
16
|
+
# C* Data Types. See http://www.datastax.com/documentation/cql/3.0/cql/cql_reference/cql_data_types_c.html
|
17
|
+
#
|
18
|
+
# ------------------------------------------------------------------------
|
19
|
+
# Migration | CQL Type | Ruby | Description
|
20
|
+
# Type | | Class |
|
21
|
+
# ------------------------------------------------------------------------
|
22
|
+
# string | varchar | String | UTF-8 encoded string
|
23
|
+
# text | text | String | UTF-8 encoded string
|
24
|
+
# ascii | ascii | String | US-ASCII character string
|
25
|
+
# ------------------------------------------------------------------------
|
26
|
+
# integer(4) | int | Integer | 32-bit signed integer
|
27
|
+
# integer(8) | bigint | Fixnum | 64-bit signed long
|
28
|
+
# varint | varint | Bignum | Arbitrary-precision integer
|
29
|
+
# ------------------------------------------------------------------------
|
30
|
+
# decimal | decimal | BigDecimal | Variable-precision decimal
|
31
|
+
# float(4) | float | | 32-bit IEEE-754 floating point
|
32
|
+
# double | double | | Float 64-bit IEEE-754 floating point
|
33
|
+
# float(8) | double | |
|
34
|
+
# ------------------------------------------------------------------------
|
35
|
+
# boolean | boolean | TrueClass | true or false
|
36
|
+
# | | FalseClass |
|
37
|
+
# ------------------------------------------------------------------------
|
38
|
+
# uuid | uuid | Cql::Uuid | A UUID in standard UUID format
|
39
|
+
# timeuuid | timeuuid | Cql::TimeUuid | Type 1 UUID only (CQL 3)
|
40
|
+
# ------------------------------------------------------------------------
|
41
|
+
# inet | inet | IPAddr | IP address string in IPv4 or
|
42
|
+
# | | | IPv6 format*
|
43
|
+
# ------------------------------------------------------------------------
|
44
|
+
# timestamp | timestamp | Time | Date plus time, encoded as 8
|
45
|
+
# | | | bytes since epoch
|
46
|
+
# datetime | timestamp | |
|
47
|
+
# ------------------------------------------------------------------------
|
48
|
+
# list | list | Array | A collection of one or more
|
49
|
+
# | | | ordered elements
|
50
|
+
# map | map | Hash | A JSON-style array of literals:
|
51
|
+
# | | | { literal : literal, ... }
|
52
|
+
# set | set | Set | A collection of one or more
|
53
|
+
# | | | elements
|
54
|
+
# binary | blob | | Arbitrary bytes (no validation),
|
55
|
+
# | | | expressed as hexadecimal
|
56
|
+
# | counter | | Distributed counter value
|
57
|
+
# | | | (64-bit long)
|
58
|
+
# ------------------------------------------------------------------------
|
59
|
+
def initialize()
|
60
|
+
@columns_name_type_hash = {}
|
61
|
+
@primary_keys = []
|
62
|
+
@partition_keys = []
|
63
|
+
@options = nil
|
64
|
+
end
|
65
|
+
|
66
|
+
def to_create_cql
|
67
|
+
cql = []
|
68
|
+
build_name_type_cql(cql)
|
69
|
+
check_for_non_key_fields_in_counter_table
|
70
|
+
build_pk_clause(cql)
|
71
|
+
cql.join(', ')
|
72
|
+
end
|
73
|
+
|
74
|
+
def to_add_column_cql
|
75
|
+
cql = ""
|
76
|
+
|
77
|
+
if @columns_name_type_hash.size == 1
|
78
|
+
cql = "#{@columns_name_type_hash.keys.first} #{@columns_name_type_hash.values.first}"
|
79
|
+
elsif @columns_name_type_hash.empty?
|
80
|
+
raise Errors::MigrationDefinitionError, 'No column to add.'
|
81
|
+
else
|
82
|
+
raise Errors::MigrationDefinitionError, 'Only one column can be added at once.'
|
83
|
+
end
|
84
|
+
|
85
|
+
cql
|
86
|
+
end
|
87
|
+
|
88
|
+
def options
|
89
|
+
@options ? " WITH %s" % (@options.map {|option| build_option(option)}.join(" AND ")) : ''
|
90
|
+
end
|
91
|
+
|
92
|
+
def boolean(column_name, options={})
|
93
|
+
@columns_name_type_hash[column_name.to_sym] = column_type_for(:boolean, options)
|
94
|
+
define_primary_keys(column_name) if options[:primary_key]
|
95
|
+
end
|
96
|
+
|
97
|
+
def integer(column_name, options={})
|
98
|
+
@columns_name_type_hash[column_name.to_sym] = column_type_for(:integer, options)
|
99
|
+
define_primary_keys(column_name) if options[:primary_key]
|
100
|
+
end
|
101
|
+
|
102
|
+
def decimal(column_name, options={})
|
103
|
+
@columns_name_type_hash[column_name.to_sym] = column_type_for(:decimal, options)
|
104
|
+
define_primary_keys(column_name) if options[:primary_key]
|
105
|
+
end
|
106
|
+
|
107
|
+
def float(column_name, options={})
|
108
|
+
@columns_name_type_hash[column_name.to_sym] = column_type_for(:float, options)
|
109
|
+
define_primary_keys(column_name) if options[:primary_key]
|
110
|
+
end
|
111
|
+
|
112
|
+
def double(column_name, options={})
|
113
|
+
options[:limit] = 8
|
114
|
+
@columns_name_type_hash[column_name.to_sym] = column_type_for(:float, options)
|
115
|
+
define_primary_keys(column_name) if options[:primary_key]
|
116
|
+
end
|
117
|
+
|
118
|
+
def string(column_name, options={})
|
119
|
+
@columns_name_type_hash[column_name.to_sym] = column_type_for(:string, options)
|
120
|
+
define_primary_keys(column_name) if options[:primary_key]
|
121
|
+
end
|
122
|
+
|
123
|
+
def text(column_name, options={})
|
124
|
+
@columns_name_type_hash[column_name.to_sym] = column_type_for(:text, options)
|
125
|
+
define_primary_keys(column_name) if options[:primary_key]
|
126
|
+
end
|
127
|
+
|
128
|
+
def ascii(column_name, options={})
|
129
|
+
@columns_name_type_hash[column_name.to_sym] = column_type_for(:ascii, options)
|
130
|
+
define_primary_keys(column_name) if options[:primary_key]
|
131
|
+
end
|
132
|
+
|
133
|
+
def datetime(column_name, options={})
|
134
|
+
@columns_name_type_hash[column_name.to_sym] = column_type_for(:datetime, options)
|
135
|
+
define_primary_keys(column_name) if options[:primary_key]
|
136
|
+
end
|
137
|
+
|
138
|
+
def timestamp(column_name, options={})
|
139
|
+
@columns_name_type_hash[column_name.to_sym] = column_type_for(:timestamp, options)
|
140
|
+
define_primary_keys(column_name) if options[:primary_key]
|
141
|
+
end
|
142
|
+
|
143
|
+
def uuid(column_name, options={})
|
144
|
+
@columns_name_type_hash[column_name.to_sym] = column_type_for(:uuid, options)
|
145
|
+
define_primary_keys(column_name) if options[:primary_key]
|
146
|
+
end
|
147
|
+
|
148
|
+
def timeuuid(column_name, options={})
|
149
|
+
@columns_name_type_hash[column_name.to_sym] = column_type_for(:timeuuid, options)
|
150
|
+
define_primary_keys(column_name) if options[:primary_key]
|
151
|
+
end
|
152
|
+
|
153
|
+
def binary(column_name, options={})
|
154
|
+
@columns_name_type_hash[column_name.to_sym] = column_type_for(:binary, options)
|
155
|
+
define_primary_keys(column_name) if options[:primary_key]
|
156
|
+
end
|
157
|
+
|
158
|
+
def counter(column_name, options={})
|
159
|
+
@columns_name_type_hash[column_name.to_sym] = column_type_for(:counter, options)
|
160
|
+
if options[:primary_key]
|
161
|
+
raise Errors::MigrationDefinitionError, 'Counter columns cannot be primary keys'
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def list(column_name, options={})
|
166
|
+
list_or_set(:list, column_name, options)
|
167
|
+
end
|
168
|
+
|
169
|
+
def set(column_name, options={})
|
170
|
+
list_or_set(:set, column_name, options)
|
171
|
+
end
|
172
|
+
|
173
|
+
def map(column_name, options={})
|
174
|
+
key_type, value_type = options[:key_type], options[:value_type]
|
175
|
+
[key_type, value_type].each_with_index do |type, index|
|
176
|
+
if type.nil?
|
177
|
+
raise Errors::MigrationDefinitionError, "A map must define a #{index = 0 ? 'key' : 'value'} type."
|
178
|
+
elsif !self.respond_to?(type)
|
179
|
+
raise Errors::MigrationDefinitionError, "Type '#{type}' is not valid for cassandra migration."
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
if options[:primary_key]
|
184
|
+
raise Errors::MigrationDefinitionError, 'A collection cannot be used as a primary key.'
|
185
|
+
end
|
186
|
+
@columns_name_type_hash[column_name.to_sym] = :"map<#{column_type_for(key_type)},#{column_type_for(value_type)}>"
|
187
|
+
end
|
188
|
+
|
189
|
+
def define_primary_keys(*keys)
|
190
|
+
if !@primary_keys.empty?
|
191
|
+
raise Errors::MigrationDefinitionError, 'Primary key defined twice for the same table.'
|
192
|
+
end
|
193
|
+
|
194
|
+
@primary_keys = keys.flatten
|
195
|
+
end
|
196
|
+
|
197
|
+
def define_partition_keys(*keys)
|
198
|
+
if !@partition_keys.empty?
|
199
|
+
raise Errors::MigrationDefinitionError, 'Partition key defined twice for the same table.'
|
200
|
+
end
|
201
|
+
|
202
|
+
@partition_keys = keys.flatten
|
203
|
+
end
|
204
|
+
|
205
|
+
def define_options(hash)
|
206
|
+
@options = hash
|
207
|
+
end
|
208
|
+
|
209
|
+
private
|
210
|
+
|
211
|
+
PASSTHROUGH_TYPES = [:text, :ascii, :decimal, :double, :boolean,
|
212
|
+
:uuid, :timeuuid, :inet, :timestamp, :list,
|
213
|
+
:map, :set, :counter]
|
214
|
+
TYPES_MAP = { string: :varchar,
|
215
|
+
datetime: :timestamp,
|
216
|
+
binary: :blob }
|
217
|
+
|
218
|
+
PRECISION_MAP = {
|
219
|
+
integer: { 4 => :int, 8 => :bigint, nil => :int },
|
220
|
+
float: { 4 => :float, 8 => :double, nil => :float }
|
221
|
+
}
|
222
|
+
|
223
|
+
SPECIAL_OPTIONS_MAP = {
|
224
|
+
compact_storage: 'COMPACT STORAGE',
|
225
|
+
clustering_order: 'CLUSTERING ORDER'
|
226
|
+
}
|
227
|
+
|
228
|
+
def column_type_for(type, options={})
|
229
|
+
cql_type = type if PASSTHROUGH_TYPES.include?(type)
|
230
|
+
cql_type ||= TYPES_MAP[type]
|
231
|
+
if PRECISION_MAP.keys.include?(type)
|
232
|
+
limit = options[:limit]
|
233
|
+
unless PRECISION_MAP[type].keys.include?(limit)
|
234
|
+
raise Errors::MigrationDefinitionError, ":limit option should be #{PRECISION_MAP[type].keys.compact.join(' or ')} for #{type}."
|
235
|
+
end
|
236
|
+
cql_type ||= PRECISION_MAP[type][limit]
|
237
|
+
end
|
238
|
+
cql_type
|
239
|
+
end
|
240
|
+
|
241
|
+
def build_option(option)
|
242
|
+
name, value = option
|
243
|
+
cql_name = SPECIAL_OPTIONS_MAP.fetch(name, name.to_s)
|
244
|
+
case name
|
245
|
+
when :clustering_order
|
246
|
+
"#{cql_name} BY (#{value})"
|
247
|
+
when :compact_storage
|
248
|
+
cql_name
|
249
|
+
else
|
250
|
+
"#{cql_name} = #{value}"
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
def build_name_type_cql(cql)
|
255
|
+
if !@columns_name_type_hash.empty?
|
256
|
+
@columns_name_type_hash.each do |column_name, type|
|
257
|
+
cql << "#{column_name} #{type}"
|
258
|
+
end
|
259
|
+
else
|
260
|
+
raise Errors::MigrationDefinitionError, 'No columns defined for table.'
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
def check_for_non_key_fields_in_counter_table
|
265
|
+
if (@columns_name_type_hash.values.include? :counter)
|
266
|
+
non_key_columns = @columns_name_type_hash.keys - @primary_keys
|
267
|
+
counter_columns = @columns_name_type_hash.select { |name, type| type == :counter }.keys
|
268
|
+
if (non_key_columns - counter_columns).present?
|
269
|
+
raise Errors::MigrationDefinitionError, 'Non key fields not allowed in tables with counter'
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def build_pk_clause(cql)
|
275
|
+
key_info = (@primary_keys - @partition_keys)
|
276
|
+
key_info = ["(#{@partition_keys.join(', ')})", *key_info] if @partition_keys.any?
|
277
|
+
|
278
|
+
if key_info.any?
|
279
|
+
cql << "PRIMARY KEY(#{key_info.join(', ')})"
|
280
|
+
else
|
281
|
+
raise Errors::MigrationDefinitionError, 'No primary key defined.'
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
def list_or_set(collection_type, column_name, options={})
|
286
|
+
type = options[:type]
|
287
|
+
if type.nil?
|
288
|
+
raise Errors::MigrationDefinitionError, "A #{collection_type} must define a collection type."
|
289
|
+
elsif !self.respond_to?(type)
|
290
|
+
raise Errors::MigrationDefinitionError, "Type '#{type}' is not valid for cassandra migration."
|
291
|
+
end
|
292
|
+
if options[:primary_key]
|
293
|
+
raise Errors::MigrationDefinitionError, 'A collection cannot be used as a primary key.'
|
294
|
+
end
|
295
|
+
@columns_name_type_hash[column_name.to_sym] = :"#{collection_type}<#{column_type_for(type)}>"
|
296
|
+
end
|
297
|
+
|
298
|
+
end
|
299
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require_relative 'table_definition'
|
2
|
+
|
3
|
+
module Cassie::Schema::Migration::DSL
|
4
|
+
# Module grouping methods used in migrations to make table operations like:
|
5
|
+
# - creating tables
|
6
|
+
# - dropping tables
|
7
|
+
module TableOperations
|
8
|
+
|
9
|
+
# Creates a new table in the keyspace
|
10
|
+
#
|
11
|
+
# options:
|
12
|
+
# - :primary_keys: single value or array (for compound primary keys). If
|
13
|
+
# not defined, some column must be chosen as primary key in the table definition.
|
14
|
+
|
15
|
+
def create_table(table_name, options = {})
|
16
|
+
table_definition = TableDefinition.new
|
17
|
+
table_definition.define_primary_keys(options[:primary_keys]) if options[:primary_keys]
|
18
|
+
table_definition.define_partition_keys(options[:partition_keys]) if options[:partition_keys]
|
19
|
+
table_definition.define_options(options[:options]) if options[:options]
|
20
|
+
|
21
|
+
yield table_definition if block_given?
|
22
|
+
|
23
|
+
announce_operation "create_table(#{table_name})"
|
24
|
+
|
25
|
+
create_cql = "CREATE TABLE #{table_name} ("
|
26
|
+
create_cql << table_definition.to_create_cql
|
27
|
+
create_cql << ")"
|
28
|
+
create_cql << table_definition.options
|
29
|
+
|
30
|
+
announce_suboperation create_cql
|
31
|
+
|
32
|
+
execute create_cql
|
33
|
+
end
|
34
|
+
|
35
|
+
def create_index(table_name, column_name, options = {})
|
36
|
+
announce_operation "create_index(#{table_name})"
|
37
|
+
create_index_cql = "CREATE INDEX #{options[:name]} ON #{table_name} (#{column_name})".squeeze(' ')
|
38
|
+
announce_suboperation create_index_cql
|
39
|
+
|
40
|
+
execute create_index_cql
|
41
|
+
end
|
42
|
+
|
43
|
+
# Drops a table
|
44
|
+
def drop_table(table_name)
|
45
|
+
announce_operation "drop_table(#{table_name})"
|
46
|
+
drop_cql = "DROP TABLE #{table_name}"
|
47
|
+
announce_suboperation drop_cql
|
48
|
+
|
49
|
+
execute drop_cql
|
50
|
+
end
|
51
|
+
|
52
|
+
def drop_index(table_or_index_name, column_name = nil, options = {})
|
53
|
+
if column_name
|
54
|
+
index_name = "#{table_or_index_name}_#{column_name}_idx"
|
55
|
+
else
|
56
|
+
index_name = table_or_index_name
|
57
|
+
end
|
58
|
+
drop_index_cql = "DROP INDEX #{options[:if_exists] ? 'IF EXISTS' : ''}#{index_name}"
|
59
|
+
announce_suboperation drop_index_cql
|
60
|
+
|
61
|
+
execute drop_index_cql
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Cassie::Schema
|
2
|
+
class Migration
|
3
|
+
module DSL
|
4
|
+
require_relative 'dsl/table_operations'
|
5
|
+
require_relative 'dsl/column_operations'
|
6
|
+
require_relative 'dsl/announcing'
|
7
|
+
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
include ColumnOperations
|
12
|
+
include TableOperations
|
13
|
+
include Announcing
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
|
3
|
+
module Cassie::Schema
|
4
|
+
require_relative 'apply_command'
|
5
|
+
require_relative 'rollback_command'
|
6
|
+
|
7
|
+
class Migrator
|
8
|
+
attr_reader :target_version, :current_version, :direction
|
9
|
+
attr_reader :commands
|
10
|
+
attr_accessor :before_each, :after_each
|
11
|
+
|
12
|
+
|
13
|
+
def initialize(target)
|
14
|
+
@target_version = build_target_version(target)
|
15
|
+
@current_version = Cassie::Schema.version
|
16
|
+
@direction = build_direction
|
17
|
+
@before_each = Proc.new{}
|
18
|
+
@after_each = Proc.new{}
|
19
|
+
@commands = send("build_#{direction}_commands")
|
20
|
+
end
|
21
|
+
|
22
|
+
def migrate
|
23
|
+
commands_with_callbacks do |command|
|
24
|
+
command.execute
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# versions applied to the database
|
29
|
+
# enumerated in most recent first order
|
30
|
+
def applied_versions
|
31
|
+
@applied_versions ||= Cassie::Schema.applied_versions.to_a
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
def local_versions
|
37
|
+
Cassie::Schema.local_versions
|
38
|
+
end
|
39
|
+
|
40
|
+
def build_target_version(target)
|
41
|
+
case target
|
42
|
+
when Version
|
43
|
+
target
|
44
|
+
when /^[\d\.]+$/
|
45
|
+
Version.new(target)
|
46
|
+
when nil
|
47
|
+
local_versions.last || Cassie::Schema.version
|
48
|
+
else
|
49
|
+
raise ArgumentError, "Migrator target must be a `Version` object, version string, or nil"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def build_direction
|
54
|
+
target_version >= current_version ? :up : :down
|
55
|
+
end
|
56
|
+
|
57
|
+
def commands_with_callbacks
|
58
|
+
commands.each do |command|
|
59
|
+
before_each.call(command.version, command.direction)
|
60
|
+
duration = Benchmark.realtime do
|
61
|
+
yield(command)
|
62
|
+
end
|
63
|
+
after_each.call(command.version, (duration*1000).round(2))
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# install all local versions since current
|
68
|
+
#
|
69
|
+
# a (current) | b | c | d (target) | e
|
70
|
+
def build_up_commands
|
71
|
+
local_versions.select{ |v| v > current_version && v <= target_version }
|
72
|
+
.map{ |v| ApplyCommand.new(v) }
|
73
|
+
end
|
74
|
+
|
75
|
+
# rollback all versions applied past the target
|
76
|
+
# and apply missing versions to get to target
|
77
|
+
#
|
78
|
+
# 0 | a (target) (not applied) | b | c | d (current) | e
|
79
|
+
def build_down_commands
|
80
|
+
rollbacks = rollback_versions.map{ |v| RollbackCommand.new(v) }
|
81
|
+
missing = missing_versions_before(rollbacks.last.version).map{ |v| ApplyCommand.new(v) }
|
82
|
+
rollbacks + missing
|
83
|
+
end
|
84
|
+
|
85
|
+
# all versions applied since target
|
86
|
+
# 0 | a (target) (not applied) | b | c | d (current) | e
|
87
|
+
def rollback_versions
|
88
|
+
applied_versions.select{ |a| a > target_version && a <= current_version }
|
89
|
+
end
|
90
|
+
|
91
|
+
# versions that are not applied yet
|
92
|
+
# but need to get applied
|
93
|
+
# to get up the target version
|
94
|
+
#
|
95
|
+
# | 0 (stop) | a (target) | b | c
|
96
|
+
def missing_versions_before(last_rollback)
|
97
|
+
return [] unless last_rollback
|
98
|
+
|
99
|
+
rollback_index = applied_versions.index(last_rollback)
|
100
|
+
|
101
|
+
stop = if rollback_index == applied_versions.length - 1
|
102
|
+
# rolled back to oldest version, a rollback
|
103
|
+
# would put us in a versionless state.
|
104
|
+
# Any versions up to target should be applied
|
105
|
+
Version.new('0')
|
106
|
+
else
|
107
|
+
applied_versions[rollback_index + 1]
|
108
|
+
end
|
109
|
+
|
110
|
+
return [] if stop == target_version
|
111
|
+
|
112
|
+
local_versions.select{ |v| v > stop && v <= target_version }
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Cassie::Schema
|
2
|
+
class CreateKeyspaceQuery < Cassie::Definition
|
3
|
+
self.prepare = false
|
4
|
+
self.keyspace = nil
|
5
|
+
|
6
|
+
attr_accessor :name,
|
7
|
+
:replication_class,
|
8
|
+
:replication_factor,
|
9
|
+
:durable_writes
|
10
|
+
|
11
|
+
def initialize(*args)
|
12
|
+
super(*args)
|
13
|
+
@replication_class ||= 'SimpleStrategy'
|
14
|
+
@replication_factor ||= 1
|
15
|
+
@durable_writes = true unless defined?(@durable_writes)
|
16
|
+
end
|
17
|
+
|
18
|
+
def statement
|
19
|
+
cql = %(
|
20
|
+
CREATE KEYSPACE IF NOT EXISTS #{name}
|
21
|
+
WITH replication = {'class': '#{replication_class}', 'replication_factor': '#{replication_factor}'}
|
22
|
+
AND durable_writes = #{!!durable_writes};
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -1,23 +1,23 @@
|
|
1
|
-
module Cassie::
|
1
|
+
module Cassie::Schema
|
2
2
|
class CreateVersionsTableQuery < Cassie::Definition
|
3
3
|
self.prepare = false
|
4
4
|
|
5
5
|
def statement
|
6
6
|
%(
|
7
|
-
CREATE TABLE #{Cassie::
|
7
|
+
CREATE TABLE #{Cassie::Schema.versions_table} (
|
8
8
|
bucket int,
|
9
9
|
id timeuuid,
|
10
|
-
|
10
|
+
number text,
|
11
11
|
description text,
|
12
|
-
|
13
|
-
|
12
|
+
executor text,
|
13
|
+
executed_at timestamp,
|
14
14
|
PRIMARY KEY (bucket, id)
|
15
15
|
) WITH CLUSTERING ORDER BY (id DESC);
|
16
16
|
)
|
17
17
|
end
|
18
18
|
|
19
19
|
def keyspace
|
20
|
-
Cassie::
|
20
|
+
Cassie::Schema.schema_keyspace
|
21
21
|
end
|
22
22
|
end
|
23
23
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Cassie::Schema
|
2
|
+
class DeleteVersionQuery < Cassie::Modification
|
3
|
+
|
4
|
+
delete_from Cassie::Schema.versions_table
|
5
|
+
|
6
|
+
where :id, :eq
|
7
|
+
where :bucket, :eq
|
8
|
+
|
9
|
+
def bucket
|
10
|
+
0
|
11
|
+
end
|
12
|
+
|
13
|
+
def keyspace
|
14
|
+
Cassie::Schema.schema_keyspace
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Cassie::Schema
|
2
|
+
class InsertVersionQuery < Cassie::Modification
|
3
|
+
# use fully-qualified tablename
|
4
|
+
# for cql generation for standalone queries
|
5
|
+
self.keyspace = nil
|
6
|
+
|
7
|
+
insert_into "#{Cassie::Schema.schema_keyspace}.#{Cassie::Schema.versions_table}"
|
8
|
+
|
9
|
+
set :bucket
|
10
|
+
set :id
|
11
|
+
set :number
|
12
|
+
set :description
|
13
|
+
set :executor
|
14
|
+
set :executed_at
|
15
|
+
|
16
|
+
map_from :version
|
17
|
+
|
18
|
+
def bucket
|
19
|
+
0
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Cassie::Schema
|
2
|
+
class SelectVersionsQuery < Cassie::Query
|
3
|
+
|
4
|
+
select_from Cassie::Schema.versions_table
|
5
|
+
|
6
|
+
def build_result(row)
|
7
|
+
Version.new(row["number"],
|
8
|
+
row["description"],
|
9
|
+
row["id"],
|
10
|
+
row["executor"],
|
11
|
+
row["executed_at"])
|
12
|
+
end
|
13
|
+
|
14
|
+
def keyspace
|
15
|
+
Cassie::Schema.schema_keyspace
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -1,8 +1,10 @@
|
|
1
|
-
module Cassie::
|
1
|
+
module Cassie::Schema
|
2
2
|
module Queries
|
3
|
-
require_relative 'queries/
|
3
|
+
require_relative 'queries/create_keyspace_query'
|
4
4
|
require_relative 'queries/create_versions_table_query'
|
5
5
|
require_relative 'queries/insert_version_query'
|
6
|
+
require_relative 'queries/delete_version_query'
|
6
7
|
require_relative 'queries/select_versions_query'
|
8
|
+
require_relative 'queries/drop_keyspace_query'
|
7
9
|
end
|
8
10
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Cassie::Schema
|
2
|
+
class RollbackCommand
|
3
|
+
attr_reader :version
|
4
|
+
|
5
|
+
def initialize(version)
|
6
|
+
@version = version
|
7
|
+
end
|
8
|
+
|
9
|
+
def direction
|
10
|
+
:down
|
11
|
+
end
|
12
|
+
|
13
|
+
def execute
|
14
|
+
version.migration.down
|
15
|
+
remove_from_history
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
def remove_from_history
|
21
|
+
Cassie::Schema.forget_version(version)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|