cass_schema 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ed62cce9c48b24a103e72a5ac83df927bee4986a
4
+ data.tar.gz: 855847953f7f9c54933ab532b7524d81b80aac40
5
+ SHA512:
6
+ metadata.gz: 90961f405aca7ae569903d92368e5f0fd1a36eb123852644734974956d8ea64d2a75e5b73e9064e4a5797e1ae71f6eda892fe3cd2084bd7745e660cf8f22bf04
7
+ data.tar.gz: 5cad1a2a5eff5824ce8817ca336ccc79e8fdcfc45121e9f99f4f30d6d9e92b0f27b7df20eef12ed850ea349307fabfa18da8190ac96a5c0ed1d5bc0d1eab4622
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,17 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in cass_migrations.gemspec
4
+ gemspec
5
+
6
+ group :development, :test do
7
+ gem "pry"
8
+ gem "awesome_print"
9
+ gem 'm', :git => 'git@github.com:ANorwell/m.git', :branch => 'minitest_5'
10
+ end
11
+
12
+ group :test do
13
+ gem 'minitest_should', :git => 'git@github.com:citrus/minitest_should.git'
14
+ gem "mocha"
15
+ end
16
+
17
+ gem 'cassandra-driver', :git => 'git@github.com:datastax/ruby-driver.git', :tag => 'v2.0.1'
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Datto
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,127 @@
1
+ # CassSchema
2
+
3
+ A gem for managing multiple cassandra schemas across multiple clusters. CassSchema supports loading a table schema from scratch, as well as running a migration to apply a change to the schema. Unlike some other database migration tools, there is no stored state about which migrations have and have not been run -- a migration is simply a CQL statement to be run against the database.
4
+
5
+ CassSchema operations apply to multiple 'datastores'. A datastore is a cluster, keyspace pair, so there may be multiple schemas for a single cluster, but only a single schema for a given cluster plus keyspace.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'cass_schema', github: 'backupify/cass_schema'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install cass_schema
22
+
23
+ ## Usage
24
+
25
+ Before usage, CassSchema must be initialized with a set of datastore objects, as well as a base directory where the schema definitions live:
26
+
27
+ ```ruby
28
+ CassSchema::Runner.initialize(
29
+ datastores: CassSchema::YamlHelper.datastores('my/path/to/config.yml'),
30
+ schema_base_path: 'my/path/to/schema/root')
31
+ ```
32
+
33
+ Here the datastores are a list of `CassSchema::DataStore` objects. These may be build manually, or loaded from a yaml file using the `CassSchema::YamlHelper`. CassSchema is intentionally agnostic about the source of these datastores.
34
+
35
+ The schema_base_path is a directory where the schema definitions and migrations live. The structure of this directory should be:
36
+ ```
37
+ <schema_base_path>/<datastore_name>/schema.cql
38
+ <schema_base_path>/<datastore_name>/migrations/<migration1>.cql
39
+ <schema_base_path>/<datastore_name>/migrations/<migration2>.cql
40
+ ...
41
+
42
+ ```
43
+
44
+ The contents of each cql file should be a list of CQL statements. Comments starting with '#' are supported, and each statement should be separated by two new lines.
45
+
46
+ schema.cql and each migration should be maintained by hand. It is recommended that schema.cql contain a complete list of CQL statements for the entire, up-to-date schema.
47
+
48
+ An example yml config and datastore schema definition are in the `test/fixtures` directory.
49
+
50
+ ### Rake tasks
51
+
52
+ The file `lib/cass_schema/tasks/schema.rake` defines several rake tasks for schema management. In a Rails project, these tasks are loaded when cass_schema is first loaded.
53
+
54
+ * `rake cass:schema:create_all` - create all schemas
55
+ * `rake cass:schema:drop_all` - drop all schemas
56
+ * `rake cass:schema:create[datastore]` - create the schema for the datastore named 'datastore'
57
+ * `rake cass:schema:drop[datastore]` - drop the schema for the datastore named 'datastore'
58
+ * `rake cass:schema:migrate[datastore, migration]` - run the migration 'migration' for the datastore 'datastore'
59
+
60
+ ### Ruby Library Usage
61
+
62
+ To create all datastore schemas:
63
+
64
+ ```
65
+ CassSchema::Runner.create_all
66
+ ```
67
+
68
+ To create a particular datastore schemas:
69
+
70
+ ```
71
+ CassSchema::Runner.create('datastore')
72
+ ```
73
+
74
+ To drop all datastore schemas:
75
+
76
+ ```
77
+ CassSchema::Runner.drop_all
78
+ ```
79
+
80
+ To drop a particular datastore schemas:
81
+
82
+ ```
83
+ CassSchema::Runner.drop('datastore')
84
+ ```
85
+
86
+ To run a particular migration for a particular datastore:
87
+
88
+ ```
89
+ CassSchema::Runner.migrate('datastore', 'migration')
90
+ ```
91
+
92
+ Here the migration file 'migration.cql' should exist.
93
+
94
+ ### Integration with other libraries
95
+
96
+ While `CassSchema::YamlLoader` provides ease of use, it is not required: the runner can be instantiated
97
+ with any list of `CassSchema::Datastore` objects. Here is an example manual set up:
98
+
99
+ ```ruby
100
+ cluster = CassSchema::Cluster.build(hosts: ['127.0.0.1'], port: 9242)
101
+ replication = "{ 'class' : 'SimpleStrategy', 'replication_factor' : 1 }"
102
+ datastores = [
103
+ CassSchema::Datastore.build('test_datastore', cluster: cluster, keyspace: 'test_keyspace', replication: replication),
104
+ CassSchema::Datastore.build('test_datastore2', cluster: cluster, keyspace: 'test_keyspace2', replication: replication),
105
+ ]
106
+ Runner.setup(datastores: datastores, schema_base_path: 'my/base/path')
107
+ ```
108
+
109
+ This allows CassSchema to be integrated into other configuration schemes more easily.
110
+
111
+ Additionally, the global `CassSchema::Runner` object need not be used: An instance of `CassSchema::Runner` can be
112
+ constructed and used in the same manner as the global object:
113
+
114
+ ```ruby
115
+ # Accepts the same arguments as Runner#setup
116
+ runner = Runner.new(datastores: datastores, schema_base_path: 'my/base/path')
117
+ runner.create_all
118
+ ```
119
+
120
+
121
+ ## Contributing
122
+
123
+ 1. Fork it ( https://github.com/backupify/cass_schema/fork )
124
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
125
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
126
+ 4. Push to the branch (`git push origin my-new-feature`)
127
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,20 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.pattern = 'test/**/*_test.rb'
7
+ t.libs.push 'test'
8
+ end
9
+
10
+ task :setup do
11
+ require 'bundler/setup'
12
+ Bundler.require(:default, :development)
13
+ end
14
+
15
+
16
+ task :console => [:setup] do
17
+ Pry.start
18
+ end
19
+
20
+ task default: :test
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'cass_schema/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "cass_schema"
8
+ spec.version = CassSchema::VERSION
9
+ spec.authors = ["Arron Norwell"]
10
+ spec.email = ["anorwell@gmail.com"]
11
+ spec.summary = %q{Manage Cassandra Schemas for multiple conceptual datastores.}
12
+ spec.description = %q{A gem for managing the schema multiple Cassandra instances}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.7"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+
24
+ spec.add_dependency 'cassandra-driver'
25
+ spec.add_dependency "activesupport"
26
+ end
@@ -0,0 +1,17 @@
1
+ require 'cassandra'
2
+
3
+ module CassSchema
4
+ # A struct representing a Cassandra cluster.
5
+ # @param [Cassandra::Cluster] connection to the cassandra cluster to be used.
6
+ Cluster = Struct.new(:connection) do
7
+ def self.build(hash)
8
+ l = hash.with_indifferent_access
9
+
10
+ if l[:hosts]
11
+ l[:connection] ||= Cassandra.cluster(hosts: l[:hosts], port: l[:port])
12
+ end
13
+
14
+ new(l[:connection])
15
+ end
16
+ end if !defined?(Cluster)
17
+ end
@@ -0,0 +1,109 @@
1
+ require_relative 'errors'
2
+ require_relative 'statement_loader'
3
+ require_relative 'cluster'
4
+ require 'active_support/core_ext/hash'
5
+ require 'active_support/core_ext/object'
6
+
7
+ module CassSchema
8
+ # A struct representing a datastore, composed of the following fields:
9
+ # @param [String] name of the datastore
10
+ # @param [CassSchema::Cluster] A list of hosts and a port representing the cluster.
11
+ # @param [String] keyspace
12
+ # @param [String] A string defining the options with which the keyspace should be created, e.g.:
13
+ # "{ 'class' : 'SimpleStrategy', 'replication_factor' : 3 }"
14
+ # @param [String] The name of the schema directory within the cass_schema directory, typically the same as the name.
15
+ # @param [String] Optional base directory to find the schema and migration files.
16
+ # statements are separated by two new lines, and a statement cannot have two newlines inside of it
17
+ # comments start with '#'
18
+ DataStore = Struct.new(:name, :cluster, :keyspace, :replication, :schema, :schema_base_path) do
19
+
20
+ attr_reader :logger
21
+
22
+ # Creates a datastore object from a hash containing the required keys
23
+ # @param [String] name of the data store
24
+ # @option [CassSchema::Cluster] :cluster
25
+ # @option [String] :schema, defines the schema directory used. Sefaults to the name if not given.
26
+ # @option [String] :keyspace
27
+ # @option [String] :replication
28
+ def self.build(name, hash)
29
+ l = hash.with_indifferent_access
30
+ schema = l[:schema] || name
31
+ new(name, l[:cluster], l[:keyspace], l[:replication], schema, l[:schema_base_path])
32
+ end
33
+
34
+ # Creates the datastore
35
+ def create
36
+ run_statement(create_keyspace, general_client)
37
+ create_statements.each { |statement| run_statement(statement, client) }
38
+ end
39
+
40
+ # Drops the datastore
41
+ def drop
42
+ run_statement(drop_keyspace, general_client)
43
+ end
44
+
45
+ # Runs a given migration for this datastore
46
+ # @param migration_name [String] the name of the migration
47
+ def migrate(migration_name)
48
+ migration_statements(migration_name).each { |statement| run_statement(statement, client) }
49
+ end
50
+
51
+ # A Cassava client connected to the cluster and keyspace with which this datastore is associated
52
+ # @return [Cassandra::Session]
53
+ def client
54
+ @client ||= cluster.connection.connect(keyspace)
55
+ end
56
+
57
+ # A Cassava client connected to the cluster with which this datastore is associated
58
+ # @return [Cassandra::Session]
59
+ def general_client
60
+ @general_client ||= cluster.connection.connect
61
+ end
62
+
63
+ # Internal method used by Runner to pass state into the datastore
64
+ def _setup(options = {})
65
+ self.schema_base_path ||= options[:schema_base_path]
66
+ @logger = options[:logger]
67
+ end
68
+
69
+ def schema_path
70
+ File.join(schema_base_path, schema, 'schema.cql')
71
+ end
72
+
73
+ def schema_base_path
74
+ self[:schema_base_path]
75
+ end
76
+
77
+ private
78
+
79
+ def run_statement(statement, client)
80
+ log("CassSchema: #{statement}")
81
+ client.execute(statement)
82
+ rescue Cassandra::Errors::ConfigurationError
83
+ # Special case if we cannot create/drop the keyspace, do nothing
84
+ rescue => e
85
+ log(e, :error)
86
+ raise SchemaError.create(e, statement)
87
+ end
88
+
89
+ def create_statements
90
+ StatementLoader.statements(schema_path)
91
+ end
92
+
93
+ def migration_statements(migration_name)
94
+ StatementLoader.statements(schema_base_path, schema, 'migrations', "#{migration_name}.cql")
95
+ end
96
+
97
+ def create_keyspace
98
+ "CREATE KEYSPACE #{keyspace} with replication = #{replication}"
99
+ end
100
+
101
+ def drop_keyspace
102
+ "DROP KEYSPACE #{keyspace}"
103
+ end
104
+
105
+ def log(msg, level = :info)
106
+ logger.try { |l| l.send(level, msg) }
107
+ end
108
+ end if !defined?(DataStore)
109
+ end
@@ -0,0 +1,7 @@
1
+ module CassSchema
2
+ class SchemaError < StandardError
3
+ def self.create(cause, statement = nil)
4
+ new("Error #{cause} when running statement: #{statement}")
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,87 @@
1
+ module CassSchema
2
+ class Runner
3
+
4
+ class DropCommandsNotAllowed < StandardError
5
+ def initialize(*_)
6
+ super('Drop commands have been disabled by the :disallow_drops option')
7
+ end
8
+ end
9
+
10
+ attr_reader :datastores, :schema_base_path, :logger, :cluster_builder, :disallow_drops
11
+
12
+ # Create a new Runner
13
+ # @option options [Array<CassSchema::Datastore>] :datastores - The list of datastore objects for which schemas
14
+ # will be managed.
15
+ # @option options [String] :schema_bath_path - The directory where schema definitions live. In a rails env,
16
+ # this defaults to <rails root>/cass_schema.
17
+ # @option options [#info|#error] :logger optional logger to use when creating schemas
18
+ # @option options [Boolean] :disallow_drops Defaults to false. If set to true,
19
+ # drop commands will raise an exception instead of executing the command.
20
+ def initialize(options = {})
21
+ options[:schema_base_path] ||= defined?(::Rails) ? File.join(::Rails.root, 'cass_schema') : nil
22
+
23
+ @datastores = options[:datastores]
24
+ @schema_base_path = options[:schema_base_path]
25
+ @logger = options[:logger]
26
+ @disallow_drops = options[:disallow_drops]
27
+
28
+ raise ":datastores is a required argument!" unless @datastores
29
+
30
+ @datastores.each { |ds| ds._setup(options) }
31
+ end
32
+
33
+ # Create all schemas for all datastores
34
+ def create_all
35
+ datastores.each { |d| d.create }
36
+ end
37
+
38
+ # Drop all schemas for all datastores
39
+ def drop_all
40
+ raise DropCommandsNotAllowed if disallow_drops
41
+ datastores.each { |d| d.drop }
42
+ end
43
+
44
+ # Create the schema for a particular datastore
45
+ # @param [String] datastore_name
46
+ def create(datastore_name)
47
+ datastore_lookup(datastore_name).create
48
+ end
49
+
50
+ # Drop the schema for a particular datastore
51
+ # @param [String] datastore_name
52
+ def drop(datastore_name)
53
+ raise DropCommandsNotAllowed if disallow_drops
54
+ datastore_lookup(datastore_name).drop
55
+ end
56
+
57
+ # Run a particular named migration for a datastore
58
+ # @param [String] datastore_name
59
+ # @param [String] migration_name
60
+ def migrate(datastore_name, migration_name)
61
+ datastore_lookup(datastore_name).migrate(migration_name)
62
+ end
63
+
64
+ # Find a datastore based on the datastore name
65
+ # @param datastore_name [String|Symbol] The datastore name
66
+ # @return [CassSchema::Datastore]
67
+ def datastore_lookup(datastore_name)
68
+ @datastore_lookup ||= Hash[datastores.map { |ds| [ds.name, ds] }]
69
+ @datastore_lookup[datastore_name.to_s] || (raise ArgumentError.new("CassSchema datastore #{datastore_name} not found"))
70
+ end
71
+
72
+ # The class methods for Runner are the same as the instance methods, which delegate to a singleton. To set up the
73
+ # singleton, call Runner#setup.
74
+ class << self
75
+ # (see Runner#initialize)
76
+ def setup(options = {})
77
+ @runner = Runner.new(options)
78
+ end
79
+
80
+ (Runner.instance_methods - Object.instance_methods).each do |method|
81
+ define_method(method) do |*args|
82
+ @runner.send(method, *args)
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,24 @@
1
+ module CassSchema
2
+ class StatementLoader
3
+ class << self
4
+ def statements(*path_parts)
5
+ file_path = File.join(path_parts)
6
+ file = File.open(file_path).read
7
+
8
+ # Parse the individual CQL statements as a list from the file. To do so:
9
+ # - assume statements are separated by two new lines
10
+ # - strip comments and empty lines from each statement
11
+ # - remove statements that are empty
12
+ statements = file.split(/\n{2,}/).map do |statement|
13
+ statement
14
+ .split(/\n/)
15
+ .select { |l| l !~ /^\s*#/ }
16
+ .select { |l| l !~ /^\s*$/ }
17
+ .join("\n")
18
+ end
19
+
20
+ statements.select { |s| s.length > 0 }
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,35 @@
1
+ require 'rake'
2
+
3
+ namespace :cass do
4
+ namespace :schema do
5
+
6
+ desc "Create the cassandra schema for all datastores"
7
+ task :create_all, [:datastore] => :environment do |t, args|
8
+ CassSchema::Runner.create_all
9
+ end
10
+
11
+ desc "Drop the cassandra schema for all datastores"
12
+ task :drop_all, [:datastore] => :environment do |t, args|
13
+ CassSchema::Runner.drop_all
14
+ end
15
+
16
+ desc "Create the cassandra schema for the specified datastore"
17
+ task :create, [:datastore] => :environment do |t, args|
18
+ raise ArgumentError.new('datastore argument required') unless args[:datastore]
19
+ CassSchema::Runner.create(args[:datastore])
20
+ end
21
+
22
+ desc "Drop the cassandra schema for the specified datastore"
23
+ task :drop, [:datastore] => :environment do |t, args|
24
+ raise ArgumentError.new('datastore argument required') unless args[:datastore]
25
+ CassSchema::Runner.drop(args[:datastore])
26
+ end
27
+
28
+ desc "Run the specified cassandra schema migration for the specified datastore"
29
+ task :migrate, [:datastore, :migration] => :environment do |t, args|
30
+ raise ArgumentError.new('datastore argument required') unless args[:datastore]
31
+ raise ArgumentError.new('migration argument required') unless args[:migration]
32
+ CassSchema::Runner.migrate(args[:datastore], args[:migration])
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,3 @@
1
+ module CassSchema
2
+ VERSION = "0.3.1"
3
+ end
@@ -0,0 +1,14 @@
1
+ require_relative 'datastore'
2
+ require 'yaml'
3
+
4
+ module CassSchema
5
+ class YamlHelper
6
+ def self.datastores(config_file)
7
+ config = YAML.load(File.read(config_file))
8
+ config['datastores'].map do |name, ds_config|
9
+ ds_config[:cluster] = Cluster.build(ds_config)
10
+ DataStore.build(name, ds_config)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,18 @@
1
+ require 'cass_schema/version'
2
+ require 'cass_schema/runner'
3
+ require 'cass_schema/datastore'
4
+ require 'cass_schema/yaml_helper'
5
+
6
+ module CassSchema; end
7
+
8
+ if defined?(::Rails)
9
+ module CassSchema
10
+ module Rails
11
+ class Railtie < ::Rails::Railtie
12
+ rake_tasks do
13
+ load "cass_schema/tasks/schema.rake"
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ require_relative '../test_helper'
2
+
3
+ module CassSchema
4
+ class DataStoreTest < MiniTest::Should::TestCase
5
+ context '#build' do
6
+ should 'accept the schema base path as an option' do
7
+ schema_base_path = File.expand_path(__FILE__, '../')
8
+ ds = CassSchema::DataStore.build("hello", schema_base_path: schema_base_path)
9
+ assert_equal File.join(schema_base_path, 'hello', 'schema.cql'), ds.schema_path
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,133 @@
1
+ require_relative '../test_helper'
2
+
3
+ module CassSchema
4
+ class RunnerTest < MiniTest::Should::TestCase
5
+
6
+ setup do
7
+ @base_path = "#{File.dirname(__FILE__)}/../fixtures"
8
+ Runner.setup(datastores: YamlHelper.datastores(File.join(@base_path, 'test_config.yml')),
9
+ schema_base_path: @base_path)
10
+ end
11
+
12
+ teardown do
13
+ Runner.drop_all unless @dont_drop_on_teardown
14
+ end
15
+
16
+ def tables_for_keyspace(keyspace)
17
+ create_client.execute('SELECT * FROM system.schema_columnfamilies').rows.to_a.select { |t| t["keyspace_name"] == keyspace}
18
+ end
19
+
20
+ def schema_for_table(keyspace, table)
21
+ create_client.execute('SELECT * FROM system.schema_columns').rows.to_a.select do |t|
22
+ (t["keyspace_name"] == 'test_keyspace') && (t['columnfamily_name'] == table)
23
+ end
24
+ end
25
+
26
+ context 'creating a schema' do
27
+ should 'be able to create a datastore' do
28
+ Runner.create('test_datastore')
29
+ tables = tables_for_keyspace('test_keyspace')
30
+ assert_equal %w(test test2).to_set, tables.map { |t| t['columnfamily_name'] }.to_set
31
+ end
32
+
33
+ should 'raise a SchemaError and not execute subsequent schema commands when a schema contains errors' do
34
+ assert_raises(SchemaError) { Runner.create('invalid_datastore') }
35
+ end
36
+
37
+ should 'raise a misisng file error if schema for a datastore does not exist' do
38
+ assert_raises(Errno::ENOENT) { Runner.create('missing_datastore') }
39
+ end
40
+
41
+ should 'raise an error if a datastore does not exist' do
42
+ assert_raises(ArgumentError) { Runner.create('nonexistent_datastore') }
43
+ end
44
+
45
+ should 'be able to create a schema for which the schema name differs from the datastore name' do
46
+ datastore = Runner.datastore_lookup('test_datastore')
47
+ datastore.schema = 'test2'
48
+ Runner.create('test_datastore')
49
+ tables = tables_for_keyspace('test_keyspace')
50
+ assert_equal %w(test_other test_other2).to_set, tables.map { |t| t['columnfamily_name'] }.to_set
51
+
52
+ datastore.schema = 'test_datastore'
53
+ end
54
+ end
55
+
56
+ context 'dropping a schema' do
57
+ should 'be able to drop a datastore' do
58
+ Runner.create('test_datastore')
59
+ Runner.drop('test_datastore')
60
+ tables = tables_for_keyspace('test_keyspace')
61
+ assert_equal [], tables.map(&:columnfamily_name)
62
+ end
63
+
64
+ should 'raise an error if a datastore does not exist' do
65
+ assert_raises(ArgumentError) { Runner.drop('nonexistent_datastore') }
66
+ end
67
+ end
68
+
69
+ context 'running a migration' do
70
+ should 'be able to run a migration for a datastore' do
71
+ Runner.create('test_datastore')
72
+ Runner.migrate('test_datastore', 'migration')
73
+ schema = schema_for_table('test_datastore', 'test')
74
+
75
+ column = schema.find { |col| col['column_name'] == 'new_column'}
76
+ assert column
77
+ assert_equal 'org.apache.cassandra.db.marshal.Int32Type', column['validator']
78
+ end
79
+
80
+ should 'raise an error if a datastore does not exist' do
81
+ assert_raises(ArgumentError) { Runner.create('nonexistent_datastore') }
82
+ end
83
+ end
84
+
85
+ context 'custom setup' do
86
+ setup do
87
+ connection = Cassandra.cluster(hosts: %w(127.0.0.1), port: 9242)
88
+ cluster = Cluster.build(connection: connection)
89
+ datastores = [DataStore.build('test_datastore',
90
+ cluster: cluster,
91
+ keyspace: 'test_keyspace',
92
+ replication: "{ 'class' : 'SimpleStrategy', 'replication_factor' : 1 }")]
93
+ @runner = Runner.new(datastores: datastores, schema_base_path: @base_path )
94
+ end
95
+
96
+ should 'be able to create a datastore' do
97
+ @runner.create('test_datastore')
98
+ tables = tables_for_keyspace('test_keyspace')
99
+ assert_equal %w(test test2).to_set, tables.map { |t| t['columnfamily_name'] }.to_set
100
+ end
101
+
102
+ should 'be able to run a migration' do
103
+ @runner.create('test_datastore')
104
+ @runner.migrate('test_datastore', 'migration')
105
+ schema = schema_for_table('test_datastore', 'test')
106
+
107
+ column = schema.find { |col| col['column_name'] == 'new_column'}
108
+ assert column
109
+ assert_equal 'org.apache.cassandra.db.marshal.Int32Type', column['validator']
110
+ end
111
+ end
112
+
113
+ context 'disallowing drops' do
114
+ setup do
115
+ Runner.setup(datastores: YamlHelper.datastores(File.join(@base_path, 'test_config.yml')),
116
+ schema_base_path: @base_path,
117
+ disallow_drops: true
118
+ )
119
+
120
+ @dont_drop_on_teardown = true
121
+
122
+ end
123
+
124
+ should 'disallow drop_all if :disallow_drops is set' do
125
+ assert_raises(CassSchema::Runner::DropCommandsNotAllowed) { Runner.drop_all }
126
+ end
127
+
128
+ should 'disallow drop if :disallow_drops is set' do
129
+ assert_raises(CassSchema::Runner::DropCommandsNotAllowed) { Runner.drop('test_keyspace') }
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,11 @@
1
+ CREATE TABLE test(
2
+ id text,
3
+ a text,
4
+ b text,
5
+ PRIMARY KEY(id, a))
6
+
7
+
8
+ CREATE TABLE test(
9
+ syntax error,
10
+ b text,
11
+ PRIMARY KEY(id, a))
@@ -0,0 +1,12 @@
1
+ CREATE TABLE test_other(
2
+ id text,
3
+ a text,
4
+ b text,
5
+ PRIMARY KEY(id, a))
6
+
7
+ # Comment
8
+ CREATE TABLE test_other2(
9
+ id text,
10
+ x text,
11
+ y text,
12
+ PRIMARY KEY(id, x))
@@ -0,0 +1,16 @@
1
+ datastores:
2
+ test_datastore:
3
+ hosts: 127.0.0.1
4
+ port: 9242
5
+ keyspace: test_keyspace
6
+ replication: "{ 'class' : 'SimpleStrategy', 'replication_factor' : 1 }"
7
+ invalid_datastore:
8
+ hosts: 127.0.0.1
9
+ port: 9242
10
+ keyspace: test_keyspace_invalid
11
+ replication: "{ 'class' : 'SimpleStrategy', 'replication_factor' : 1 }"
12
+ missing_datastore:
13
+ hosts: 127.0.0.1
14
+ port: 9242
15
+ keyspace: test_keyspace_invalid
16
+ replication: "{ 'class' : 'SimpleStrategy', 'replication_factor' : 1 }"
@@ -0,0 +1 @@
1
+ ALTER TABLE test ADD new_column int;
@@ -0,0 +1,14 @@
1
+ # CREATE KEYSPACE test_keyspace with replication = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 }
2
+
3
+ CREATE TABLE test(
4
+ id text,
5
+ a text,
6
+ b text,
7
+ PRIMARY KEY(id, a))
8
+
9
+ # Comment
10
+ CREATE TABLE test2(
11
+ id text,
12
+ x text,
13
+ y text,
14
+ PRIMARY KEY(id, x))
@@ -0,0 +1,18 @@
1
+ require 'rubygems'
2
+ require 'pry'
3
+ require 'minitest/autorun'
4
+ require 'minitest/should'
5
+
6
+
7
+ require 'cass_schema'
8
+
9
+ class Minitest::Should::TestCase
10
+ def self.xshould(*args)
11
+ puts "Disabled test: #{args}"
12
+ end
13
+ end
14
+
15
+ def create_client
16
+ c = Cassandra.cluster(port: 9242)
17
+ c.connect
18
+ end
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cass_schema
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.1
5
+ platform: ruby
6
+ authors:
7
+ - Arron Norwell
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-05-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: cassandra-driver
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: activesupport
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: A gem for managing the schema multiple Cassandra instances
70
+ email:
71
+ - anorwell@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - Gemfile
78
+ - LICENSE.txt
79
+ - README.md
80
+ - Rakefile
81
+ - cass_schema.gemspec
82
+ - lib/cass_schema.rb
83
+ - lib/cass_schema/cluster.rb
84
+ - lib/cass_schema/datastore.rb
85
+ - lib/cass_schema/errors.rb
86
+ - lib/cass_schema/runner.rb
87
+ - lib/cass_schema/statement_loader.rb
88
+ - lib/cass_schema/tasks/schema.rake
89
+ - lib/cass_schema/version.rb
90
+ - lib/cass_schema/yaml_helper.rb
91
+ - test/cass_schema/datastore_test.rb
92
+ - test/cass_schema/runner_test.rb
93
+ - test/fixtures/invalid_datastore/schema.cql
94
+ - test/fixtures/test2/schema.cql
95
+ - test/fixtures/test_config.yml
96
+ - test/fixtures/test_datastore/migrations/migration.cql
97
+ - test/fixtures/test_datastore/schema.cql
98
+ - test/test_helper.rb
99
+ homepage: ''
100
+ licenses:
101
+ - MIT
102
+ metadata: {}
103
+ post_install_message:
104
+ rdoc_options: []
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ requirements: []
118
+ rubyforge_project:
119
+ rubygems_version: 2.4.8
120
+ signing_key:
121
+ specification_version: 4
122
+ summary: Manage Cassandra Schemas for multiple conceptual datastores.
123
+ test_files:
124
+ - test/cass_schema/datastore_test.rb
125
+ - test/cass_schema/runner_test.rb
126
+ - test/fixtures/invalid_datastore/schema.cql
127
+ - test/fixtures/test2/schema.cql
128
+ - test/fixtures/test_config.yml
129
+ - test/fixtures/test_datastore/migrations/migration.cql
130
+ - test/fixtures/test_datastore/schema.cql
131
+ - test/test_helper.rb