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,117 @@
|
|
1
|
+
module Cassie::Schema
|
2
|
+
require_relative 'queries'
|
3
|
+
|
4
|
+
class StructureDumper
|
5
|
+
attr_reader :destination_path
|
6
|
+
|
7
|
+
def initialize(opts={})
|
8
|
+
@destination_path = opts[:destination_path] || default_destination_path
|
9
|
+
end
|
10
|
+
|
11
|
+
def stream
|
12
|
+
@stream ||= begin
|
13
|
+
prepare_stream
|
14
|
+
File.open(destination_path, "w+")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Fetch the CQL that can be used to recreate the current environment's keyspace
|
19
|
+
# @return [String] CQL commands
|
20
|
+
# @raise [RuntimeError] if the {Cassie.configuration[:keyspace]} keyspace could not be described.
|
21
|
+
def keyspace_structure
|
22
|
+
@keyspace_structure ||= begin
|
23
|
+
args = ["-e", "'DESCRIBE KEYSPACE #{Cassie.configuration[:keyspace]}'"]
|
24
|
+
runner = Cassie::Support::SystemCommand.new("cqlsh", args)
|
25
|
+
runner.succeed
|
26
|
+
|
27
|
+
runner.output
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Fetch the CQL that can be used to recreat the schema metadata keyspace,
|
32
|
+
# if it has been defined. If it could not be fetched (likely because it doesn't exist),
|
33
|
+
# an empty string is returned.
|
34
|
+
# @return [String] CQL commands
|
35
|
+
def schema_meta_structure
|
36
|
+
@schema_meta_structure ||= begin
|
37
|
+
args = ["-e", "'DESCRIBE KEYSPACE #{Cassie::Schema.schema_keyspace}'"]
|
38
|
+
runner = Cassie::Support::SystemCommand.new("cqlsh", args)
|
39
|
+
runner.run
|
40
|
+
|
41
|
+
if runner.success?
|
42
|
+
runner.output
|
43
|
+
else
|
44
|
+
""
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def versions
|
50
|
+
@versions ||= begin
|
51
|
+
versions_query.fetch
|
52
|
+
rescue Cassandra::Errors::InvalidError => e
|
53
|
+
log_versions_not_found(e)
|
54
|
+
[]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def versions_insert_cql
|
59
|
+
inserts = versions.map do |v|
|
60
|
+
InsertVersionQuery.new(version: v).to_cql
|
61
|
+
end
|
62
|
+
inserts.join("\n")
|
63
|
+
end
|
64
|
+
|
65
|
+
# Dump the CQL for the current environment's keyspace,
|
66
|
+
# the schema metadata keyspace, and the versions rows
|
67
|
+
# that are currently in the schema versions table.
|
68
|
+
def dump
|
69
|
+
stream << keyspace_structure
|
70
|
+
stream << schema_meta_structure
|
71
|
+
stream << "\n\n"
|
72
|
+
stream << versions_insert_cql
|
73
|
+
stream << "\n"
|
74
|
+
|
75
|
+
close_stream
|
76
|
+
end
|
77
|
+
|
78
|
+
def versions_table_name
|
79
|
+
"#{Cassie::Schema.schema_keyspace}.#{Cassie::Schema.versions_table}"
|
80
|
+
end
|
81
|
+
|
82
|
+
def destination_path
|
83
|
+
@destination_path || raise("Unconfigured schema file path: `Cassie::Schema.paths[:schema_file]` is empty")
|
84
|
+
end
|
85
|
+
|
86
|
+
protected
|
87
|
+
|
88
|
+
def versions_query
|
89
|
+
SelectVersionsQuery.new
|
90
|
+
end
|
91
|
+
|
92
|
+
def default_destination_path
|
93
|
+
Cassie::Schema.paths[:schema_file]
|
94
|
+
end
|
95
|
+
|
96
|
+
def prepare_stream
|
97
|
+
dir = File.dirname(destination_path)
|
98
|
+
Dir.mkdir(dir) unless File.directory?(dir)
|
99
|
+
end
|
100
|
+
|
101
|
+
def close_stream
|
102
|
+
stream.close
|
103
|
+
@stream = nil
|
104
|
+
end
|
105
|
+
|
106
|
+
def log_versions_not_found(error)
|
107
|
+
msg = "WARNING: Cassie Schema Versions table not found at '#{versions_table_name}'. Initialize your schema with `cassie schema:init` or `cassie:migrations:import` for versioned migration support."
|
108
|
+
msg << "\n\t- "
|
109
|
+
msg << error.message.split("\n").join("\n\t- ")
|
110
|
+
logger.warn(msg)
|
111
|
+
end
|
112
|
+
|
113
|
+
def logger
|
114
|
+
Cassie.logger
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
module Cassie::
|
1
|
+
module Cassie::Schema
|
2
2
|
class StructureLoader
|
3
3
|
attr_reader :source_path
|
4
4
|
|
@@ -9,7 +9,7 @@ module Cassie::Migration
|
|
9
9
|
|
10
10
|
def load
|
11
11
|
args = ["-f", source_path]
|
12
|
-
runner = Cassie::Support::
|
12
|
+
runner = Cassie::Support::SystemCommand.new("cqlsh", args)
|
13
13
|
|
14
14
|
runner.run
|
15
15
|
raise runner.failure_message unless runner.success?
|
@@ -18,7 +18,7 @@ module Cassie::Migration
|
|
18
18
|
protected
|
19
19
|
|
20
20
|
def default_source_path
|
21
|
-
Cassie.paths[:
|
21
|
+
Cassie::Schema.paths[:schema_file]
|
22
22
|
end
|
23
23
|
end
|
24
24
|
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
module Cassie::Schema
|
2
|
+
class Version
|
3
|
+
include Comparable
|
4
|
+
PARTS = [:major, :minor, :patch, :build].freeze
|
5
|
+
|
6
|
+
# The version uuid, if persisted
|
7
|
+
# @return [Cassandra::TimeUuid]
|
8
|
+
attr_accessor :id
|
9
|
+
# The major, minor, patch, and build parts making up the semantic version
|
10
|
+
# @return [Array<Fixnum>]
|
11
|
+
attr_accessor :parts
|
12
|
+
# The description of the changes introduced in this version
|
13
|
+
# @return [String]
|
14
|
+
attr_accessor :description
|
15
|
+
# The OS username of the user that migrated this version up
|
16
|
+
# @return [String]
|
17
|
+
attr_accessor :executor
|
18
|
+
# The time this version was migrated up
|
19
|
+
# @return [DateTime]
|
20
|
+
attr_accessor :executed_at
|
21
|
+
|
22
|
+
|
23
|
+
def initialize(version_number, description=nil, id=nil, executor=nil, executed_at=nil)
|
24
|
+
@parts = build_parts(version_number)
|
25
|
+
@description = description
|
26
|
+
@id = id
|
27
|
+
@executor = executor
|
28
|
+
@executed_at = executed_at
|
29
|
+
end
|
30
|
+
|
31
|
+
def number
|
32
|
+
parts.join('.')
|
33
|
+
end
|
34
|
+
|
35
|
+
# The major part of the semantic version
|
36
|
+
# @!parse attr_reader :major
|
37
|
+
def major
|
38
|
+
parts[0].to_i
|
39
|
+
end
|
40
|
+
|
41
|
+
# The minor part of the semantic version
|
42
|
+
# @!parse attr_reader :minor
|
43
|
+
def minor
|
44
|
+
parts[1].to_i
|
45
|
+
end
|
46
|
+
|
47
|
+
# The patch part of the semantic version
|
48
|
+
# @!parse attr_reader :patch
|
49
|
+
def patch
|
50
|
+
parts[2].to_i
|
51
|
+
end
|
52
|
+
|
53
|
+
# The build part of the semantic version
|
54
|
+
# @!parse attr_reader :build
|
55
|
+
def build
|
56
|
+
parts[3].to_i
|
57
|
+
end
|
58
|
+
|
59
|
+
# Builds a new version, wiht a version number incremented from this
|
60
|
+
# object's version. Does not propogate any other attributes
|
61
|
+
# @option bump_type [Symbol] :build Bump the build version
|
62
|
+
# @option bump_type [Symbol] :patch Bump the patch version, set build to 0
|
63
|
+
# @option bump_type [Symbol] :minor Bump the minor version, set patch and build to 0
|
64
|
+
# @option bump_type [Symbol] :major Bump the major version, set minor, patch, and build to 0
|
65
|
+
# @option bump_type [nil] nil Default, bumps patch, sets build to 0
|
66
|
+
# @return [Version]
|
67
|
+
def next(bump_type=nil)
|
68
|
+
bump_type ||= :patch
|
69
|
+
bump_index = PARTS.index(bump_type.to_sym)
|
70
|
+
|
71
|
+
# 0.2.1 - > 0.2
|
72
|
+
bumped_parts = parts.take(bump_index + 1)
|
73
|
+
# 0.2 - > 0.3
|
74
|
+
bumped_parts[bump_index] = bumped_parts[bump_index].to_i + 1
|
75
|
+
# 0.3 - > 0.3.0.0
|
76
|
+
bumped_parts += [0]*(PARTS.length - (bump_index + 1))
|
77
|
+
self.class.new(bumped_parts.join('.'))
|
78
|
+
end
|
79
|
+
|
80
|
+
# Compares versions by semantic version number
|
81
|
+
def <=>(other)
|
82
|
+
case other
|
83
|
+
when Version
|
84
|
+
Gem::Version.new(self.number) <=> Gem::Version.new(other.number)
|
85
|
+
when String
|
86
|
+
Gem::Version.new(self.number) <=> Gem::Version.new(other)
|
87
|
+
else
|
88
|
+
nil
|
89
|
+
end
|
90
|
+
end
|
91
|
+
alias :eql? :==
|
92
|
+
|
93
|
+
# @return [Boolean] indicating whether the version has been persisted
|
94
|
+
# in the schema metatdata versions persistence store.
|
95
|
+
def recorded?
|
96
|
+
!!self.id
|
97
|
+
end
|
98
|
+
|
99
|
+
def hash
|
100
|
+
parts.hash
|
101
|
+
end
|
102
|
+
|
103
|
+
# The migration class name, as implied by the version number
|
104
|
+
# @example 1.2.3
|
105
|
+
# migration_class_name
|
106
|
+
# #=> "Migration_1_2_3_0"
|
107
|
+
def migration_class_name
|
108
|
+
"Migration_#{major}_#{minor}_#{patch}_#{build}"
|
109
|
+
end
|
110
|
+
|
111
|
+
# The migration associated with this version
|
112
|
+
# @return [Cassie::Schema::Migration, nil] if the expected migration class is not defined
|
113
|
+
# @!parse attr_reader :migration
|
114
|
+
def migration
|
115
|
+
@migration ||= begin
|
116
|
+
migration_class_name.constantize.new
|
117
|
+
rescue NameError
|
118
|
+
nil
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def to_h
|
123
|
+
{
|
124
|
+
id: id,
|
125
|
+
number: number,
|
126
|
+
description: description,
|
127
|
+
executor: executor,
|
128
|
+
executed_at: executed_at
|
129
|
+
}
|
130
|
+
end
|
131
|
+
|
132
|
+
def to_s
|
133
|
+
number
|
134
|
+
end
|
135
|
+
|
136
|
+
protected
|
137
|
+
|
138
|
+
def build_parts(version_number)
|
139
|
+
included = version_number.to_s.split('.').map{|p| p.to_i}
|
140
|
+
included + [0]*(PARTS.length - included.length)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require_relative 'version_loader'
|
2
|
+
|
3
|
+
module Cassie::Schema
|
4
|
+
class VersionFileLoader < VersionLoader
|
5
|
+
|
6
|
+
def initialize(filename)
|
7
|
+
@filename = filename
|
8
|
+
@version = build_version
|
9
|
+
end
|
10
|
+
|
11
|
+
protected
|
12
|
+
|
13
|
+
def build_version
|
14
|
+
unapplied_version = build_unapplied_version
|
15
|
+
|
16
|
+
# return the applied version if it exists, since it will
|
17
|
+
# have the full and current information about the version
|
18
|
+
# fall back to this unapplied_version if version is not applied
|
19
|
+
Cassie::Schema.applied_versions.find{ |v| v == unapplied_version } || unapplied_version
|
20
|
+
rescue Cassie::Schema::UninitializedError => e
|
21
|
+
# version cannot be applied if cassie schema meta is not initialized
|
22
|
+
unapplied_version
|
23
|
+
end
|
24
|
+
|
25
|
+
def build_unapplied_version
|
26
|
+
matches = File.basename(filename).match(/([0-9_]+)_?(.*).rb$/).captures
|
27
|
+
|
28
|
+
number = matches.first.tr('_','.')
|
29
|
+
description = matches.last.try(:humanize)
|
30
|
+
|
31
|
+
Version.new(number, description)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'etc'
|
2
|
+
|
3
|
+
module Cassie::Schema
|
4
|
+
class VersionLoader
|
5
|
+
attr_reader :filename, :version
|
6
|
+
|
7
|
+
# Requires the ruby file, thus loading the Migration class into the ObjectSpace.
|
8
|
+
#
|
9
|
+
# @return [Version, Boolean] The Version object if successful. In other words, if
|
10
|
+
# object representing the version returns a Cassie::Schema::Migration object.
|
11
|
+
# Otherwise returns false.
|
12
|
+
#
|
13
|
+
# @raise [NameError] if the migration class could not be loaded
|
14
|
+
def load
|
15
|
+
return false unless filename
|
16
|
+
require filename
|
17
|
+
|
18
|
+
begin
|
19
|
+
# ensure the migration class is now defined
|
20
|
+
version.migration_class_name.constantize
|
21
|
+
if version.migration.is_a?(Cassie::Schema::Migration)
|
22
|
+
version
|
23
|
+
else
|
24
|
+
false
|
25
|
+
end
|
26
|
+
rescue NameError
|
27
|
+
raise NameError.new("Expected #{version.migration_class_name} to be defined in #{filename}, but it was not.")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'etc'
|
2
|
+
require_relative 'version_loader'
|
3
|
+
|
4
|
+
module Cassie::Schema
|
5
|
+
class VersionObjectLoader < VersionLoader
|
6
|
+
|
7
|
+
def initialize(version)
|
8
|
+
@version = version
|
9
|
+
@filename = build_filename
|
10
|
+
end
|
11
|
+
|
12
|
+
protected
|
13
|
+
|
14
|
+
def build_filename
|
15
|
+
path = VersionWriter.new(version).existing_file
|
16
|
+
File.absolute_path(path) if path
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module Cassie::Schema
|
4
|
+
class VersionWriter
|
5
|
+
attr_reader :io
|
6
|
+
attr_reader :version
|
7
|
+
|
8
|
+
attr_accessor :up_code
|
9
|
+
attr_accessor :down_code
|
10
|
+
|
11
|
+
def initialize(version, io=nil)
|
12
|
+
@io = io
|
13
|
+
@version = version
|
14
|
+
@up_code = default_up_code
|
15
|
+
@down_code = default_down_code
|
16
|
+
|
17
|
+
ensure_dir_exist
|
18
|
+
end
|
19
|
+
|
20
|
+
def write
|
21
|
+
with_io do |io|
|
22
|
+
io << migration_contents
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def with_io
|
27
|
+
if io.respond_to?(:<<)
|
28
|
+
yield io
|
29
|
+
else
|
30
|
+
ensure_unique_version
|
31
|
+
File.open(filename, 'w'){ |file| yield file }
|
32
|
+
# load the file, so the definition for source
|
33
|
+
# for the methods will come from that file
|
34
|
+
# instead of (eval)
|
35
|
+
load File.absolute_path(filename)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def migration_contents=(contents)
|
40
|
+
@migration_contents = contents
|
41
|
+
end
|
42
|
+
|
43
|
+
def migration_contents
|
44
|
+
return @migration_contents if defined?(@migration_contents)
|
45
|
+
build_migration_contents
|
46
|
+
end
|
47
|
+
|
48
|
+
def filename
|
49
|
+
"#{directory}/#{basename}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def directory
|
53
|
+
Cassie::Schema.paths["migrations_directory"]
|
54
|
+
end
|
55
|
+
|
56
|
+
def basename
|
57
|
+
"#{version_prefix}#{description_suffix}.rb"
|
58
|
+
end
|
59
|
+
|
60
|
+
def existing_file
|
61
|
+
Dir.glob("#{directory}/#{version_prefix}*.rb").first
|
62
|
+
end
|
63
|
+
|
64
|
+
protected
|
65
|
+
|
66
|
+
def ensure_dir_exist
|
67
|
+
FileUtils.makedirs(directory) unless File.directory?(directory)
|
68
|
+
end
|
69
|
+
|
70
|
+
def ensure_unique_version
|
71
|
+
if existing_file
|
72
|
+
raise IOError.new("A migration already exists for #{version.parts.join('.')} in #{existing_file}. Try bumping the version.")
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def build_migration_contents
|
77
|
+
return @migration_contents if defined?(@migration_contents)
|
78
|
+
<<-EOS
|
79
|
+
class #{version.migration_class_name} < Cassie::Schema::Migration
|
80
|
+
def up
|
81
|
+
#{up_code}
|
82
|
+
end
|
83
|
+
|
84
|
+
def down
|
85
|
+
#{down_code}
|
86
|
+
end
|
87
|
+
end
|
88
|
+
EOS
|
89
|
+
end
|
90
|
+
|
91
|
+
def version_prefix
|
92
|
+
version.parts.map{|p| p.to_s.rjust(4, "0") }.join('_')
|
93
|
+
end
|
94
|
+
|
95
|
+
def description_suffix
|
96
|
+
return nil unless version.description
|
97
|
+
"_" + version.description.parameterize('_')
|
98
|
+
end
|
99
|
+
|
100
|
+
def default_up_code
|
101
|
+
"# Code to execute when applying this migration"
|
102
|
+
end
|
103
|
+
|
104
|
+
def default_down_code
|
105
|
+
"# Code to execute when rolling back this migration"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
require 'etc'
|
2
|
+
require_relative 'migration'
|
3
|
+
require_relative 'version_writer'
|
4
|
+
require_relative 'version_file_loader'
|
5
|
+
require_relative 'version_object_loader'
|
6
|
+
require_relative 'migrator'
|
7
|
+
|
8
|
+
module Cassie::Schema
|
9
|
+
require_relative 'version'
|
10
|
+
|
11
|
+
class AlreadyInitiailizedError < StandardError; end
|
12
|
+
class UninitializedError < StandardError; end
|
13
|
+
|
14
|
+
module Versioning
|
15
|
+
|
16
|
+
# The current schema version
|
17
|
+
# @return [Version]
|
18
|
+
def version
|
19
|
+
SelectVersionsQuery.new.fetch_first || Version.new('0')
|
20
|
+
rescue Cassandra::Errors::InvalidError
|
21
|
+
raise uninitialized_error
|
22
|
+
end
|
23
|
+
|
24
|
+
# The versions that have been migrated up for the Cassandra database
|
25
|
+
# This lists the versions stored in the persistence layer, in reverse
|
26
|
+
# chronological order (newest first).
|
27
|
+
# @return [Enumerable<Version>]
|
28
|
+
def applied_versions
|
29
|
+
@applied_versions ||= load_applied_versions
|
30
|
+
end
|
31
|
+
|
32
|
+
# Create the keyspace and table for tracking schema versions
|
33
|
+
# in the Cassandra database
|
34
|
+
# @return [void]
|
35
|
+
def initialize_versioning
|
36
|
+
create_schema_keyspace unless keyspace_exists?
|
37
|
+
raise Cassie::Schema::AlreadyInitiailizedError if version_exists?
|
38
|
+
create_versions_table unless versions_table_exists?
|
39
|
+
end
|
40
|
+
|
41
|
+
# Record a version in the schema version store.
|
42
|
+
# This should only be done if the version has been sucesfully migrated
|
43
|
+
# @param [Version] The version to record
|
44
|
+
# @param [Boolean] set_execution_metadata Determines whether or not to populate
|
45
|
+
# the version object with execution tracking info (+id+, +executed_at+, and +executor+).
|
46
|
+
# @return [Boolean] whether succesfull or not
|
47
|
+
# @raises [StandardError] if version could not be recorded. If this happens,
|
48
|
+
# execution_metadata will not be preset on the version object.
|
49
|
+
def record_version(version, set_execution_metadata=true)
|
50
|
+
version.id ||= Cassandra::TimeUuid::Generator.new.at(time)
|
51
|
+
if set_execution_metadata
|
52
|
+
time = Time.now
|
53
|
+
version.executed_at = time
|
54
|
+
version.executor = Etc.getlogin rescue '<unknown>'
|
55
|
+
end
|
56
|
+
|
57
|
+
InsertVersionQuery.new(version: version).execute!
|
58
|
+
@applied_versions = nil
|
59
|
+
rescue StandardError
|
60
|
+
version.id = nil
|
61
|
+
version.executed_at = nil
|
62
|
+
version.executor = nil
|
63
|
+
end
|
64
|
+
|
65
|
+
# Remove the version from the schema version store.
|
66
|
+
# This should only be done if the version has been sucesfully reverted
|
67
|
+
# @param [Version] version A persisted version
|
68
|
+
# @return [Boolean] whether succesfull or not
|
69
|
+
def forget_version(version)
|
70
|
+
DeleteVersionQuery.new(id: version.id).execute
|
71
|
+
@applied_versions = nil
|
72
|
+
end
|
73
|
+
|
74
|
+
# Absolute paths to the migration files in the migration directory
|
75
|
+
# @return [Array<String>]
|
76
|
+
def migration_files
|
77
|
+
Dir[root.join(paths["migrations_directory"], "[0-9]*_*.rb")]
|
78
|
+
end
|
79
|
+
|
80
|
+
# Versions for the {#migration_files}
|
81
|
+
# If a migration is applied versions, the object for that
|
82
|
+
# version will be the applied version, containing the full
|
83
|
+
# information about the applied version
|
84
|
+
# @return [Enumeration<Version>]
|
85
|
+
def local_versions
|
86
|
+
@local_versions ||= load_local_versions
|
87
|
+
end
|
88
|
+
|
89
|
+
# A version with an incremented version number that would be
|
90
|
+
# applied after the latest (local or applied) migration.
|
91
|
+
# @param [Symbol, nil] bump_type Which semantic version to bump
|
92
|
+
# @option bump_type [Symbol] :build Bump the build version
|
93
|
+
# @option bump_type [Symbol] :patch Bump the patch version, set build to 0
|
94
|
+
# @option bump_type [Symbol] :minor Bump the minor version, set patch and build to 0
|
95
|
+
# @option bump_type [Symbol] :major Bump the major version, set minor, patch, and build to 0
|
96
|
+
# @option bump_type [nil] nil Default, bumps patch, sets build to 0
|
97
|
+
# @return [Version] The initialized, bumped version
|
98
|
+
def next_version(bump_type=nil)
|
99
|
+
local_max = local_versions.max || Version.new('0')
|
100
|
+
applied_max = applied_versions.max || Version.new('0')
|
101
|
+
max_version = [local_max, applied_max].max
|
102
|
+
max_version.next(bump_type)
|
103
|
+
end
|
104
|
+
|
105
|
+
protected
|
106
|
+
|
107
|
+
def default_version
|
108
|
+
'0.0.1.0'
|
109
|
+
end
|
110
|
+
|
111
|
+
def keyspace_exists?
|
112
|
+
Cassie.keyspace_exists?(Cassie::Schema.schema_keyspace)
|
113
|
+
end
|
114
|
+
|
115
|
+
# load version migration class from disk
|
116
|
+
def load_applied_versions
|
117
|
+
database_versions.tap do |versions|
|
118
|
+
versions.each{|v| VersionObjectLoader.new(v).load }
|
119
|
+
end
|
120
|
+
rescue Cassandra::Errors::InvalidError
|
121
|
+
raise uninitialized_error
|
122
|
+
end
|
123
|
+
|
124
|
+
def database_versions
|
125
|
+
SelectVersionsQuery.new.fetch
|
126
|
+
end
|
127
|
+
|
128
|
+
def load_local_versions
|
129
|
+
migration_files.map do |filename|
|
130
|
+
VersionFileLoader.new(filename).load
|
131
|
+
end.sort!
|
132
|
+
end
|
133
|
+
|
134
|
+
def version_exists?
|
135
|
+
!!Cassie::Schema.version
|
136
|
+
rescue Cassie::Schema::UninitializedError
|
137
|
+
false
|
138
|
+
end
|
139
|
+
|
140
|
+
def versions_table_exists?
|
141
|
+
!!SelectVersionsQuery.new(limit: 1).fetch
|
142
|
+
rescue Cassandra::Errors::InvalidError
|
143
|
+
false
|
144
|
+
end
|
145
|
+
|
146
|
+
def create_schema_keyspace
|
147
|
+
CreateKeyspaceQuery.new(name: Cassie::Schema.schema_keyspace).execute
|
148
|
+
end
|
149
|
+
|
150
|
+
def create_versions_table
|
151
|
+
CreateVersionsTableQuery.new.execute
|
152
|
+
end
|
153
|
+
|
154
|
+
def uninitialized_error
|
155
|
+
UninitializedError.new(uninitialized_message)
|
156
|
+
end
|
157
|
+
|
158
|
+
def uninitialized_message
|
159
|
+
"Cassie Schema Versions table not found at '#{schema_keyspace}.#{versions_table}'. Enable versioned migration support with `cassie schema:init`."
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Cassie
|
2
|
+
# Contains interface and components for managing
|
3
|
+
# Cassandra schema using semantically versioned,
|
4
|
+
# incremental migration files.
|
5
|
+
#
|
6
|
+
# * Versioned migration files are stored in-repo in ruby files defining +up+ and +down+ mutation methods.
|
7
|
+
# * Data about what migrations have been applied is stored in Cassandra persistence.
|
8
|
+
# * The schema state is stored in an in-repo schema file that contains the CQL required to recreate the current schema state/version from scratch.
|
9
|
+
# * Various +cassie+ executable commands provide an interface to manage migrations and versioning.
|
10
|
+
#
|
11
|
+
# Run +cassie --help+ to see a list of commands and their descriptions for managing the schema through versioned migrations.
|
12
|
+
#
|
13
|
+
# @see file:lib/cassie/schema/README.md Schema README for information on task usage and the migration DSL.
|
14
|
+
module Schema
|
15
|
+
require_relative 'schema/configuration'
|
16
|
+
require_relative 'schema/versioning'
|
17
|
+
|
18
|
+
extend Configuration
|
19
|
+
extend Versioning
|
20
|
+
end
|
21
|
+
|
22
|
+
require_relative 'schema/structure_dumper'
|
23
|
+
require_relative 'schema/structure_loader'
|
24
|
+
end
|