cassanity 0.5.1 → 0.6.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +1 -1
- data/README.md +3 -16
- data/cassanity.gemspec +3 -1
- data/examples/keyspaces.rb +2 -2
- data/lib/cassanity/argument_generators/column_families.rb +1 -1
- data/lib/cassanity/argument_generators/columns.rb +2 -2
- data/lib/cassanity/argument_generators/keyspace_create.rb +4 -21
- data/lib/cassanity/argument_generators/with_clause.rb +2 -9
- data/lib/cassanity/client.rb +16 -13
- data/lib/cassanity/executors/{cassandra_cql.rb → cql_rb.rb} +28 -11
- data/lib/cassanity/keyspace.rb +7 -22
- data/lib/cassanity/migrator.rb +2 -2
- data/lib/cassanity/result_transformers/column_families.rb +1 -1
- data/lib/cassanity/result_transformers/columns.rb +2 -2
- data/lib/cassanity/result_transformers/keyspaces.rb +1 -1
- data/lib/cassanity/result_transformers/result_to_array.rb +1 -5
- data/lib/cassanity/retry_strategies/retry_strategy.rb +3 -3
- data/lib/cassanity/statement.rb +58 -0
- data/lib/cassanity/version.rb +1 -1
- data/spec/helper.rb +2 -4
- data/spec/integration/cassanity/column_family_spec.rb +62 -64
- data/spec/integration/cassanity/connection_spec.rb +2 -8
- data/spec/integration/cassanity/instrumentation/log_subscriber_spec.rb +4 -1
- data/spec/integration/cassanity/instrumentation/metriks_subscriber_spec.rb +2 -1
- data/spec/integration/cassanity/instrumentation/statsd_subscriber_spec.rb +2 -1
- data/spec/integration/cassanity/keyspace_spec.rb +1 -1
- data/spec/integration/cassanity/migration_spec.rb +5 -5
- data/spec/integration/cassanity/migrator_spec.rb +4 -4
- data/spec/support/cassanity_helpers.rb +7 -5
- data/spec/unit/cassanity/argument_generators/column_families_spec.rb +1 -1
- data/spec/unit/cassanity/argument_generators/columns_spec.rb +3 -3
- data/spec/unit/cassanity/argument_generators/keyspace_create_spec.rb +12 -16
- data/spec/unit/cassanity/argument_generators/with_clause_spec.rb +5 -6
- data/spec/unit/cassanity/client_spec.rb +15 -53
- data/spec/unit/cassanity/connection_spec.rb +6 -6
- data/spec/unit/cassanity/keyspace_spec.rb +12 -14
- data/spec/unit/cassanity/result_transformers/result_to_array_spec.rb +3 -16
- data/spec/unit/cassanity/statement_spec.rb +100 -0
- metadata +35 -24
- data/spec/unit/cassanity/executors/cassandra_cql_spec.rb +0 -286
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7892f054f8690a99ae75b121cc43d873c917566a
|
4
|
+
data.tar.gz: 19c266c25056ef75b019463cca2d9bb49133ea72
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 76cbbe3e3afdf0e741325d8e48fd11b51ac08718ab60f423a6ebf6f31c067a48382598605652ab3cedc3e30cdc401f3f0c7860981f82d49749e6fe9d50755a2c
|
7
|
+
data.tar.gz: 909558dee131c53a6c648f2f3148ac44d00f2065bbd33049a46923f793dcac6b36f1b3cd45385b1be96bafa452a927ec7c7467629b4a300b1427649bd83b8431
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,25 +1,13 @@
|
|
1
|
-
# Cassanity
|
1
|
+
# Cassanity [![Build Status](https://secure.travis-ci.org/jnunemaker/cassanity.png?branch=master)](http://travis-ci.org/jnunemaker/cassanity)
|
2
2
|
|
3
3
|
Layer of goodness on top of cassandra-cql so you do not have to write CQL strings all over the place.
|
4
4
|
|
5
|
-
## Note about Cassandra 1.2
|
6
|
-
|
7
|
-
At this time, cassandra 1.2 is not supported. Under the hood, cassanity uses [cassandra-cql](https://github.com/kreynolds/cassandra-cql), which does not currently support 1.2. It needs the thrift bindings ([initial pull request](https://github.com/kreynolds/cassandra-cql/pull/39)) updated for 1.2 or to wrap the new binary protocol ([initial pull request](https://github.com/kreynolds/cassandra-cql/pull/40)). I'm hoping to work on this in February or March, but if you want to take a stab, that would be awesome.
|
8
|
-
|
9
5
|
## Installation
|
10
6
|
|
11
7
|
Add this line to your application's Gemfile:
|
12
8
|
|
13
9
|
gem 'cassanity'
|
14
10
|
|
15
|
-
And then execute:
|
16
|
-
|
17
|
-
$ bundle
|
18
|
-
|
19
|
-
Or install it yourself as:
|
20
|
-
|
21
|
-
$ gem install cassanity
|
22
|
-
|
23
11
|
## Usage
|
24
12
|
|
25
13
|
```ruby
|
@@ -110,9 +98,8 @@ You can also do a lot more. Here are a few more [examples](https://github.com/jn
|
|
110
98
|
|
111
99
|
## Compatibility
|
112
100
|
|
113
|
-
* Ruby 1.9.3
|
114
|
-
* Cassandra CQL 3.
|
115
|
-
* Any version of cassandra that works with cassandra-cql and supports CQL 3.
|
101
|
+
* Ruby >= 1.9.3
|
102
|
+
* Cassandra >= 1.2 with CQL >= 3.1.0
|
116
103
|
|
117
104
|
## Contributing
|
118
105
|
|
data/cassanity.gemspec
CHANGED
@@ -17,5 +17,7 @@ Gem::Specification.new do |gem|
|
|
17
17
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
18
|
gem.require_paths = ["lib"]
|
19
19
|
|
20
|
-
gem.add_dependency '
|
20
|
+
gem.add_dependency 'cql-rb', '~>1.1', '>=1.1.1'
|
21
|
+
|
22
|
+
gem.add_development_dependency 'simple_uuid', '~>0.4'
|
21
23
|
end
|
data/examples/keyspaces.rb
CHANGED
@@ -15,11 +15,11 @@ module Cassanity
|
|
15
15
|
cql = 'SELECT * FROM system.schema_columns'
|
16
16
|
|
17
17
|
if (keyspace_name = args[:keyspace_name])
|
18
|
-
where[:
|
18
|
+
where[:keyspace_name] = keyspace_name
|
19
19
|
end
|
20
20
|
|
21
21
|
if (column_family_name = args[:column_family_name])
|
22
|
-
where[:
|
22
|
+
where[:columnfamily_name] = column_family_name
|
23
23
|
end
|
24
24
|
|
25
25
|
where_cql, *where_variables = @where_clause.call(where: where)
|
@@ -14,22 +14,9 @@ module Cassanity
|
|
14
14
|
name = args.fetch(:keyspace_name)
|
15
15
|
cql = "CREATE KEYSPACE #{name}"
|
16
16
|
|
17
|
-
|
18
|
-
strategy_class: default_strategy_class,
|
19
|
-
strategy_options: default_strategy_options,
|
20
|
-
}
|
21
|
-
|
22
|
-
if args[:strategy_class]
|
23
|
-
with[:strategy_class] = args[:strategy_class]
|
24
|
-
end
|
25
|
-
|
26
|
-
if args[:strategy_options]
|
27
|
-
args[:strategy_options].each do |key, value|
|
28
|
-
with[:strategy_options][key] = value
|
29
|
-
end
|
30
|
-
end
|
17
|
+
replication = default_replication_options.merge(args[:replication] || {})
|
31
18
|
|
32
|
-
with_cql, *with_variables = @with_clause.call(with:
|
19
|
+
with_cql, *with_variables = @with_clause.call(with: { replication: replication })
|
33
20
|
cql << with_cql
|
34
21
|
variables.concat(with_variables)
|
35
22
|
|
@@ -37,13 +24,9 @@ module Cassanity
|
|
37
24
|
end
|
38
25
|
|
39
26
|
# Private
|
40
|
-
def
|
41
|
-
'SimpleStrategy'
|
42
|
-
end
|
43
|
-
|
44
|
-
# Private
|
45
|
-
def default_strategy_options
|
27
|
+
def default_replication_options
|
46
28
|
{
|
29
|
+
class: 'SimpleStrategy',
|
47
30
|
replication_factor: 1,
|
48
31
|
}
|
49
32
|
end
|
@@ -17,15 +17,8 @@ module Cassanity
|
|
17
17
|
withs << "COMPACT STORAGE"
|
18
18
|
end
|
19
19
|
else
|
20
|
-
|
21
|
-
|
22
|
-
withs << "#{key}:#{sub_key} = ?"
|
23
|
-
variables << sub_value
|
24
|
-
end
|
25
|
-
else
|
26
|
-
withs << "#{key} = ?"
|
27
|
-
variables << value
|
28
|
-
end
|
20
|
+
withs << "#{key} = ?"
|
21
|
+
variables << value
|
29
22
|
end
|
30
23
|
end
|
31
24
|
|
data/lib/cassanity/client.rb
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
require 'forwardable'
|
2
|
-
require '
|
3
|
-
require 'cassanity/executors/
|
2
|
+
require 'cql'
|
3
|
+
require 'cassanity/executors/cql_rb'
|
4
4
|
require 'cassanity/connection'
|
5
5
|
|
6
6
|
module Cassanity
|
7
7
|
class Client
|
8
8
|
extend Forwardable
|
9
9
|
|
10
|
-
# Public: The instance of the
|
10
|
+
# Public: The instance of the Cql::Client being used.
|
11
11
|
attr_reader :driver
|
12
12
|
|
13
|
-
# Public: The instance of the Cassanity::Executors::
|
13
|
+
# Public: The instance of the Cassanity::Executors::CqlRb that will
|
14
14
|
# execute all queries.
|
15
15
|
attr_reader :executor
|
16
16
|
|
@@ -22,18 +22,21 @@ module Cassanity
|
|
22
22
|
#
|
23
23
|
# servers - The String or Array of Strings representing the servers to
|
24
24
|
# connect to.
|
25
|
-
#
|
26
|
-
#
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
25
|
+
# port - The Integer representing the port to connect to Cassandra with.
|
26
|
+
# This must be the same for all hosts.
|
27
|
+
# options - The Hash of Cql::Client options.
|
28
|
+
# :default_consistency - default consistency for the connection
|
29
|
+
# (if not specified, set to :quorum)
|
30
|
+
def initialize(servers = nil, port = nil, options = {})
|
31
|
+
servers ||= ['127.0.0.1']
|
32
|
+
port ||= 9042
|
33
|
+
|
34
|
+
@options = options.merge(hosts: servers, port: port)
|
31
35
|
@instrumenter = @options.delete(:instrumenter)
|
32
36
|
@retry_strategy = @options.delete(:retry_strategy)
|
33
37
|
|
34
|
-
@driver =
|
35
|
-
|
36
|
-
@executor = Cassanity::Executors::CassandraCql.new({
|
38
|
+
@driver = Cql::Client.connect(@options)
|
39
|
+
@executor = Cassanity::Executors::CqlRb.new({
|
37
40
|
driver: @driver,
|
38
41
|
instrumenter: @instrumenter,
|
39
42
|
retry_strategy: @retry_strategy,
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'forwardable'
|
2
|
-
require '
|
2
|
+
require 'cql'
|
3
3
|
require 'cassanity/error'
|
4
|
+
require 'cassanity/statement'
|
4
5
|
require 'cassanity/instrumenters/noop'
|
5
6
|
require 'cassanity/argument_generators/keyspaces'
|
6
7
|
require 'cassanity/argument_generators/keyspace_create'
|
@@ -29,7 +30,7 @@ require 'cassanity/retry_strategies/exponential_backoff'
|
|
29
30
|
|
30
31
|
module Cassanity
|
31
32
|
module Executors
|
32
|
-
class
|
33
|
+
class CqlRb
|
33
34
|
extend Forwardable
|
34
35
|
|
35
36
|
# Private: Hash of commands to related argument generators.
|
@@ -88,7 +89,7 @@ module Cassanity
|
|
88
89
|
# Internal: Initializes a cassandra-cql based CQL executor.
|
89
90
|
#
|
90
91
|
# args - The Hash of arguments.
|
91
|
-
# :driver - The
|
92
|
+
# :driver - The Cql::Client connection instance.
|
92
93
|
# :instrumenter - What should be used to instrument all the things
|
93
94
|
# (default: Cassanity::Instrumenters::Noop).
|
94
95
|
# :argument_generators - A Hash where each key is a command name
|
@@ -100,13 +101,13 @@ module Cassanity
|
|
100
101
|
# transformer that responds to `call`
|
101
102
|
# (optional).
|
102
103
|
# :retry_strategy - What retry strategy to use on failed
|
103
|
-
#
|
104
|
+
# Cql::Client calls
|
104
105
|
# (default: Cassanity::Instrumenters::RetryNTimes)
|
105
106
|
#
|
106
107
|
# Examples
|
107
108
|
#
|
108
|
-
# driver =
|
109
|
-
# Cassanity::Executors::
|
109
|
+
# driver = Cql::Client.connect(hosts: ['cassandra.example.com'])
|
110
|
+
# Cassanity::Executors::CqlRb.new(driver: driver)
|
110
111
|
#
|
111
112
|
def initialize(args = {})
|
112
113
|
@driver = args.fetch(:driver)
|
@@ -148,20 +149,36 @@ module Cassanity
|
|
148
149
|
arguments = args[:arguments]
|
149
150
|
|
150
151
|
if arguments
|
152
|
+
# TODO: As a temporary measure, we remove this deprecated option
|
153
|
+
# while we have time to update each gem (e.g., adapter-cassanity)
|
154
|
+
# that sets it. Consistency should be specified at the connection
|
155
|
+
# level for now.
|
156
|
+
if arguments[:using]
|
157
|
+
arguments[:using].delete(:consistency)
|
158
|
+
end
|
159
|
+
|
160
|
+
# Instrumentation parameters
|
151
161
|
if (keyspace_name = arguments[:keyspace_name])
|
152
162
|
payload[:keyspace_name] = keyspace_name
|
153
163
|
end
|
154
|
-
|
155
164
|
if (column_family_name = arguments[:column_family_name])
|
156
165
|
payload[:column_family_name] = column_family_name
|
157
166
|
end
|
167
|
+
|
168
|
+
# Select keyspace before query runs
|
169
|
+
if command != :keyspace_create && (keyspace_name = arguments[:keyspace_name])
|
170
|
+
@driver.use(keyspace_name)
|
171
|
+
end
|
158
172
|
end
|
159
173
|
|
160
174
|
begin
|
161
|
-
|
162
|
-
payload[:cql] =
|
163
|
-
payload[:cql_variables] =
|
164
|
-
|
175
|
+
cql, *variables = generator.call(arguments)
|
176
|
+
payload[:cql] = cql
|
177
|
+
payload[:cql_variables] = variables
|
178
|
+
|
179
|
+
statement = Cassanity::Statement.new(cql)
|
180
|
+
result = @retry_strategy.execute(payload) { @driver.execute(statement.interpolate(variables)) }
|
181
|
+
|
165
182
|
transformer = @result_transformers.fetch(command, Mirror)
|
166
183
|
transformed_result = transformer.call(result, args[:transformer_arguments])
|
167
184
|
payload[:result] = transformed_result
|
data/lib/cassanity/keyspace.rb
CHANGED
@@ -9,26 +9,20 @@ module Cassanity
|
|
9
9
|
attr_reader :executor
|
10
10
|
|
11
11
|
# Internal
|
12
|
-
attr_reader :
|
13
|
-
|
14
|
-
# Internal
|
15
|
-
attr_reader :strategy_options
|
12
|
+
attr_reader :replication
|
16
13
|
|
17
14
|
# Public: Initializes a Keyspace.
|
18
15
|
#
|
19
16
|
# args - The Hash of arguments (default: {}).
|
20
17
|
# :name - The String name of the keyspace.
|
21
18
|
# :executor - What will execute the queries. Must respond to `call`.
|
22
|
-
# :
|
23
|
-
#
|
24
|
-
# :strategy_options - The Hash of strategy options to use when
|
25
|
-
# creating keyspace.
|
19
|
+
# :replication - Hash of replication options (e.g., :class,
|
20
|
+
# :replication_factor)
|
26
21
|
#
|
27
22
|
def initialize(args = {})
|
28
23
|
@name = args.fetch(:name).to_sym
|
29
24
|
@executor = args.fetch(:executor)
|
30
|
-
@
|
31
|
-
@strategy_options = args[:strategy_options]
|
25
|
+
@replication = args.fetch(:replication, {})
|
32
26
|
end
|
33
27
|
|
34
28
|
# Public: Returns true or false depending on if keyspace exists in the
|
@@ -57,8 +51,8 @@ module Cassanity
|
|
57
51
|
#
|
58
52
|
# # override options from initialization
|
59
53
|
# create({
|
60
|
-
#
|
61
|
-
#
|
54
|
+
# replication: {
|
55
|
+
# class: 'NetworkTopologyStrategy',
|
62
56
|
# dc1: 1,
|
63
57
|
# dc2: 3,
|
64
58
|
# }
|
@@ -67,16 +61,7 @@ module Cassanity
|
|
67
61
|
# Returns whatever is returned by executor.
|
68
62
|
def create(args = {})
|
69
63
|
create_arguments = {}.merge(args)
|
70
|
-
|
71
|
-
if @strategy_class
|
72
|
-
create_arguments[:strategy_class] = @strategy_class
|
73
|
-
end
|
74
|
-
|
75
|
-
if @strategy_options
|
76
|
-
strategy_options = args[:strategy_options] || {}
|
77
|
-
create_arguments[:strategy_options] = strategy_options.merge(@strategy_options)
|
78
|
-
end
|
79
|
-
|
64
|
+
create_arguments[:replication] = @replication.merge(create_arguments[:replication] || {})
|
80
65
|
create_arguments[:keyspace_name] = @name
|
81
66
|
|
82
67
|
@executor.call({
|
data/lib/cassanity/migrator.rb
CHANGED
@@ -52,7 +52,7 @@ module Cassanity
|
|
52
52
|
def migrated_up(migration)
|
53
53
|
column_family.insert({
|
54
54
|
data: {
|
55
|
-
version: migration.version,
|
55
|
+
version: migration.version.to_s,
|
56
56
|
name: migration.name,
|
57
57
|
migrated_at: Time.now.utc,
|
58
58
|
},
|
@@ -63,7 +63,7 @@ module Cassanity
|
|
63
63
|
def migrated_down(migration)
|
64
64
|
column_family.delete({
|
65
65
|
where: {
|
66
|
-
version: migration.version,
|
66
|
+
version: migration.version.to_s,
|
67
67
|
name: migration.name,
|
68
68
|
},
|
69
69
|
})
|
@@ -7,7 +7,7 @@ module Cassanity
|
|
7
7
|
# Internal: Turns result into Array of column families.
|
8
8
|
def call(result, args = {})
|
9
9
|
column_families = []
|
10
|
-
result.
|
10
|
+
result.each do |row|
|
11
11
|
column_families << ColumnFamily.new({
|
12
12
|
name: row['columnfamily'] || row['columnfamily_name'],
|
13
13
|
keyspace: args[:keyspace],
|
@@ -7,9 +7,9 @@ module Cassanity
|
|
7
7
|
# Internal: Turns result into Array of column families.
|
8
8
|
def call(result, args = {})
|
9
9
|
columns = []
|
10
|
-
result.
|
10
|
+
result.each do |row|
|
11
11
|
columns << Column.new({
|
12
|
-
name: row['
|
12
|
+
name: row['column_name'],
|
13
13
|
type: row['validator'],
|
14
14
|
column_family: args[:column_family],
|
15
15
|
})
|
@@ -7,7 +7,7 @@ module Cassanity
|
|
7
7
|
# Internal: Turns result into Array of keyspaces.
|
8
8
|
def call(result, args = {})
|
9
9
|
keyspaces = []
|
10
|
-
result.
|
10
|
+
result.each do |row|
|
11
11
|
name = row['name'] || row['keyspace'] || row['keyspace_name']
|
12
12
|
keyspaces << Keyspace.new({
|
13
13
|
name: name,
|
@@ -10,7 +10,7 @@ module Cassanity
|
|
10
10
|
end
|
11
11
|
|
12
12
|
# Public: execute the given block, and handle errors raised
|
13
|
-
# by the
|
13
|
+
# by the Cql::Client driver. Call the retry method (overridden in your
|
14
14
|
# subclass) on each failed attempt with a current retry count and
|
15
15
|
# the error raised by the block.
|
16
16
|
#
|
@@ -25,11 +25,11 @@ module Cassanity
|
|
25
25
|
begin
|
26
26
|
payload[:attempts] = attempt unless payload.nil?
|
27
27
|
return yield
|
28
|
-
rescue
|
28
|
+
rescue Cql::CqlError => e
|
29
29
|
fail(attempt, e)
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end
|
33
33
|
end
|
34
34
|
end
|
35
|
-
end
|
35
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'bigdecimal'
|
2
|
+
require 'date'
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
module Cassanity
|
6
|
+
class Statement
|
7
|
+
def initialize(cql, options = {})
|
8
|
+
@cql = cql
|
9
|
+
@options = options
|
10
|
+
|
11
|
+
@cql_version = @options.fetch(:cql_version, '3.0.0')
|
12
|
+
end
|
13
|
+
|
14
|
+
def interpolate(variables)
|
15
|
+
e = variables.to_enum
|
16
|
+
@cql.gsub(/\?/) { quote(e.next) }
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
def quote(var)
|
21
|
+
if Array === var
|
22
|
+
var.map { |v| quote(v) }.join(',')
|
23
|
+
elsif Hash === var
|
24
|
+
%({#{var.map { |k, v| "#{quote(k)}:#{quote(v)}" }.join(',')}})
|
25
|
+
elsif String === var
|
26
|
+
%('#{escape_string(var)}')
|
27
|
+
elsif BigDecimal === var && cql2?
|
28
|
+
%('#{var.to_s}')
|
29
|
+
elsif Numeric === var
|
30
|
+
var.to_s
|
31
|
+
elsif Date === var
|
32
|
+
%('#{var.strftime('%Y-%m-%d')}')
|
33
|
+
elsif Time === var
|
34
|
+
(var.to_f * 1000).to_i
|
35
|
+
elsif TrueClass === var || FalseClass === var
|
36
|
+
if cql2?
|
37
|
+
%('#{var.to_s}')
|
38
|
+
else
|
39
|
+
var.to_s
|
40
|
+
end
|
41
|
+
elsif var.respond_to?(:to_guid)
|
42
|
+
var.to_guid
|
43
|
+
elsif var.respond_to?(:to_s)
|
44
|
+
%('#{var.to_s}')
|
45
|
+
else
|
46
|
+
raise ArgumentError, "Unable to escape #{var} (of type #{var.class})"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def escape_string(str)
|
51
|
+
str.gsub("'", "''")
|
52
|
+
end
|
53
|
+
|
54
|
+
def cql2?
|
55
|
+
@cql_version.start_with?('2')
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/cassanity/version.rb
CHANGED
data/spec/helper.rb
CHANGED
@@ -29,7 +29,5 @@ RSpec.configure do |config|
|
|
29
29
|
config.include CassanityHelpers
|
30
30
|
end
|
31
31
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
CassanityServers = "#{host}:#{port}"
|
32
|
+
CassanityHost = Array(ENV.fetch('CASSANITY_HOST', '127.0.0.1'))
|
33
|
+
CassanityPort = ENV.fetch('CASSANITY_PORT', '9042').to_i
|