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.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/bin/cassie +8 -181
  3. data/lib/cassie/configuration/core.rb +26 -3
  4. data/lib/cassie/configuration/generator.rb +1 -0
  5. data/lib/cassie/configuration/loading.rb +5 -2
  6. data/lib/cassie/configuration.rb +1 -0
  7. data/lib/cassie/connection.rb +13 -7
  8. data/lib/cassie/connection_handler/README.md +13 -3
  9. data/lib/cassie/connection_handler/cluster.rb +11 -0
  10. data/lib/cassie/connection_handler/sessions.rb +9 -0
  11. data/lib/cassie/connection_handler.rb +8 -7
  12. data/lib/cassie/definition.rb +28 -0
  13. data/lib/cassie/extensions/object/color_methods.rb +21 -0
  14. data/lib/cassie/instrumentation.rb +4 -0
  15. data/lib/cassie/modification.rb +29 -0
  16. data/lib/cassie/query.rb +27 -0
  17. data/lib/cassie/schema/README.md +306 -0
  18. data/lib/cassie/schema/apply_command.rb +24 -0
  19. data/lib/cassie/schema/cassandra_migrations/importer.rb +91 -0
  20. data/lib/cassie/schema/cassandra_migrations/migration_file.rb +51 -0
  21. data/lib/cassie/schema/configuration.rb +35 -0
  22. data/lib/cassie/schema/migration/cassandra_support.rb +34 -0
  23. data/lib/cassie/schema/migration/dsl/announcing.rb +47 -0
  24. data/lib/cassie/schema/migration/dsl/column_operations.rb +42 -0
  25. data/lib/cassie/schema/migration/dsl/table_definition.rb +299 -0
  26. data/lib/cassie/schema/migration/dsl/table_operations.rb +64 -0
  27. data/lib/cassie/schema/migration/dsl.rb +17 -0
  28. data/lib/cassie/schema/migration.rb +12 -0
  29. data/lib/cassie/schema/migrator.rb +115 -0
  30. data/lib/cassie/schema/queries/create_keyspace_query.rb +26 -0
  31. data/lib/cassie/{migration → schema}/queries/create_versions_table_query.rb +6 -6
  32. data/lib/cassie/schema/queries/delete_version_query.rb +17 -0
  33. data/lib/cassie/schema/queries/drop_keyspace_query.rb +14 -0
  34. data/lib/cassie/schema/queries/insert_version_query.rb +22 -0
  35. data/lib/cassie/schema/queries/select_versions_query.rb +18 -0
  36. data/lib/cassie/{migration → schema}/queries.rb +4 -2
  37. data/lib/cassie/schema/rollback_command.rb +24 -0
  38. data/lib/cassie/schema/structure_dumper.rb +117 -0
  39. data/lib/cassie/{migration → schema}/structure_loader.rb +3 -3
  40. data/lib/cassie/schema/version.rb +143 -0
  41. data/lib/cassie/schema/version_file_loader.rb +34 -0
  42. data/lib/cassie/schema/version_loader.rb +31 -0
  43. data/lib/cassie/schema/version_object_loader.rb +19 -0
  44. data/lib/cassie/schema/version_writer.rb +108 -0
  45. data/lib/cassie/schema/versioning.rb +162 -0
  46. data/lib/cassie/schema.rb +24 -0
  47. data/lib/cassie/statements/README.md +61 -9
  48. data/lib/cassie/statements/core.rb +16 -5
  49. data/lib/cassie/statements/execution/results/core.rb +1 -1
  50. data/lib/cassie/statements/execution/results/modification_result.rb +1 -1
  51. data/lib/cassie/statements/execution/results/query_result.rb +1 -1
  52. data/lib/cassie/statements/execution.rb +40 -13
  53. data/lib/cassie/statements/statement/assignments.rb +33 -3
  54. data/lib/cassie/statements/statement/conditions.rb +3 -1
  55. data/lib/cassie/statements/statement/deleting.rb +27 -19
  56. data/lib/cassie/statements/statement/idempotency.rb +23 -4
  57. data/lib/cassie/statements/statement/inserting.rb +17 -10
  58. data/lib/cassie/statements/statement/limiting.rb +5 -2
  59. data/lib/cassie/statements/statement/mapping.rb +34 -6
  60. data/lib/cassie/statements/statement/preparation/cache.rb +1 -1
  61. data/lib/cassie/statements/statement/preparation.rb +37 -7
  62. data/lib/cassie/statements/statement/relations.rb +29 -8
  63. data/lib/cassie/statements/statement/selection.rb +51 -15
  64. data/lib/cassie/statements/statement/type_hinting.rb +12 -4
  65. data/lib/cassie/statements/statement/updating.rb +22 -8
  66. data/lib/cassie/statements/statement.rb +39 -14
  67. data/lib/cassie/statements.rb +12 -0
  68. data/lib/cassie/support/server_process.rb +117 -0
  69. data/lib/cassie/support/statement_parser.rb +3 -5
  70. data/lib/cassie/support/{command_runner.rb → system_command.rb} +22 -13
  71. data/lib/cassie/support.rb +3 -1
  72. data/lib/cassie/tasks/configuration/generate.rake +35 -0
  73. data/lib/cassie/tasks/io.rb +15 -0
  74. data/lib/cassie/tasks/migration/create.rake +49 -0
  75. data/lib/cassie/tasks/migration/import.rake +39 -0
  76. data/lib/cassie/tasks/migration/reset.rake +9 -0
  77. data/lib/cassie/tasks/restart.rake +5 -0
  78. data/lib/cassie/tasks/schema/drop.rake +28 -0
  79. data/lib/cassie/tasks/schema/dump.rake +21 -0
  80. data/lib/cassie/tasks/schema/history.rake +18 -0
  81. data/lib/cassie/tasks/schema/import.rake +40 -0
  82. data/lib/cassie/tasks/schema/init.rake +54 -0
  83. data/lib/cassie/tasks/schema/load.rake +19 -0
  84. data/lib/cassie/tasks/schema/migrate.rake +42 -0
  85. data/lib/cassie/tasks/schema/reset.rake +6 -0
  86. data/lib/cassie/tasks/schema/status.rake +19 -0
  87. data/lib/cassie/tasks/schema/version.rake +18 -0
  88. data/lib/cassie/tasks/schema/version_display.rb +50 -0
  89. data/lib/cassie/tasks/start.rake +17 -0
  90. data/lib/cassie/tasks/stop.rake +33 -0
  91. data/lib/cassie/tasks/tail.rake +14 -0
  92. data/lib/cassie/tasks/task_runner.rb +49 -0
  93. data/lib/cassie/tasks.rb +18 -0
  94. data/lib/cassie/testing/fake/execution_info.rb +1 -1
  95. data/lib/cassie/testing/fake/result.rb +3 -3
  96. data/lib/cassie/testing.rb +4 -0
  97. data/lib/cassie/version.rb +1 -1
  98. data/lib/cassie.rb +4 -1
  99. metadata +73 -17
  100. data/lib/cassie/migration/README.md +0 -141
  101. data/lib/cassie/migration/configuration.rb +0 -18
  102. data/lib/cassie/migration/initialization.rb +0 -70
  103. data/lib/cassie/migration/queries/create_schema_keyspace_query.rb +0 -17
  104. data/lib/cassie/migration/queries/insert_version_query.rb +0 -23
  105. data/lib/cassie/migration/queries/select_versions_query.rb +0 -14
  106. data/lib/cassie/migration/structure_dumper.rb +0 -94
  107. data/lib/cassie/migration/version.rb +0 -4
  108. 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,12 @@
1
+ module Cassie::Schema
2
+ class Migration
3
+ require_relative 'migration/cassandra_support'
4
+
5
+ include CassandraSupport
6
+
7
+ def path
8
+ method = method(:up) || method(:down)
9
+ method.source_location.first
10
+ end
11
+ end
12
+ 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::Migration
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::Migration.versions_table} (
7
+ CREATE TABLE #{Cassie::Schema.versions_table} (
8
8
  bucket int,
9
9
  id timeuuid,
10
- version_number text,
10
+ number text,
11
11
  description text,
12
- migrator text,
13
- migrated_at timestamp,
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::Migration.schema_keyspace
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,14 @@
1
+ module Cassie::Schema
2
+ class DropKeyspaceQuery < Cassie::Definition
3
+ self.prepare = false
4
+ consistency :all
5
+
6
+ attr_accessor :keyspace
7
+
8
+ def statement
9
+ cql = %(
10
+ DROP KEYSPACE IF EXISTS #{keyspace};
11
+ )
12
+ end
13
+ end
14
+ 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::Migration
1
+ module Cassie::Schema
2
2
  module Queries
3
- require_relative 'queries/create_schema_keyspace_query'
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