cass_schema 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
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