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,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::Migration
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::CommandRunner.new("cqlsh", args)
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[:schema_structure]
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