cassanity 0.5.1 → 0.6.0.beta1

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.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +1 -1
  3. data/README.md +3 -16
  4. data/cassanity.gemspec +3 -1
  5. data/examples/keyspaces.rb +2 -2
  6. data/lib/cassanity/argument_generators/column_families.rb +1 -1
  7. data/lib/cassanity/argument_generators/columns.rb +2 -2
  8. data/lib/cassanity/argument_generators/keyspace_create.rb +4 -21
  9. data/lib/cassanity/argument_generators/with_clause.rb +2 -9
  10. data/lib/cassanity/client.rb +16 -13
  11. data/lib/cassanity/executors/{cassandra_cql.rb → cql_rb.rb} +28 -11
  12. data/lib/cassanity/keyspace.rb +7 -22
  13. data/lib/cassanity/migrator.rb +2 -2
  14. data/lib/cassanity/result_transformers/column_families.rb +1 -1
  15. data/lib/cassanity/result_transformers/columns.rb +2 -2
  16. data/lib/cassanity/result_transformers/keyspaces.rb +1 -1
  17. data/lib/cassanity/result_transformers/result_to_array.rb +1 -5
  18. data/lib/cassanity/retry_strategies/retry_strategy.rb +3 -3
  19. data/lib/cassanity/statement.rb +58 -0
  20. data/lib/cassanity/version.rb +1 -1
  21. data/spec/helper.rb +2 -4
  22. data/spec/integration/cassanity/column_family_spec.rb +62 -64
  23. data/spec/integration/cassanity/connection_spec.rb +2 -8
  24. data/spec/integration/cassanity/instrumentation/log_subscriber_spec.rb +4 -1
  25. data/spec/integration/cassanity/instrumentation/metriks_subscriber_spec.rb +2 -1
  26. data/spec/integration/cassanity/instrumentation/statsd_subscriber_spec.rb +2 -1
  27. data/spec/integration/cassanity/keyspace_spec.rb +1 -1
  28. data/spec/integration/cassanity/migration_spec.rb +5 -5
  29. data/spec/integration/cassanity/migrator_spec.rb +4 -4
  30. data/spec/support/cassanity_helpers.rb +7 -5
  31. data/spec/unit/cassanity/argument_generators/column_families_spec.rb +1 -1
  32. data/spec/unit/cassanity/argument_generators/columns_spec.rb +3 -3
  33. data/spec/unit/cassanity/argument_generators/keyspace_create_spec.rb +12 -16
  34. data/spec/unit/cassanity/argument_generators/with_clause_spec.rb +5 -6
  35. data/spec/unit/cassanity/client_spec.rb +15 -53
  36. data/spec/unit/cassanity/connection_spec.rb +6 -6
  37. data/spec/unit/cassanity/keyspace_spec.rb +12 -14
  38. data/spec/unit/cassanity/result_transformers/result_to_array_spec.rb +3 -16
  39. data/spec/unit/cassanity/statement_spec.rb +100 -0
  40. metadata +35 -24
  41. 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
@@ -4,7 +4,7 @@ gemspec
4
4
  gem 'dotenv'
5
5
  gem 'rake'
6
6
  gem 'rspec', '~> 2.8'
7
- gem 'activesupport', :require => false
7
+ gem 'activesupport', '~>3.2', :require => false
8
8
  gem 'metriks', :require => false
9
9
  gem 'statsd-ruby', :require => false
10
10
 
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.x
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 'cassandra-cql', '~> 1.1.3'
20
+ gem.add_dependency 'cql-rb', '~>1.1', '>=1.1.1'
21
+
22
+ gem.add_development_dependency 'simple_uuid', '~>0.4'
21
23
  end
@@ -15,8 +15,8 @@ pp keyspace
15
15
 
16
16
  # you can also provide options
17
17
  keyspace = client.keyspace('cassanity_examples', {
18
- strategy_class: 'SimpleStrategy',
19
- strategy_options: {
18
+ replication: {
19
+ class: 'SimpleStrategy',
20
20
  replication_factor: 1,
21
21
  },
22
22
  })
@@ -8,7 +8,7 @@ module Cassanity
8
8
  cql = 'SELECT * FROM system.schema_columnfamilies'
9
9
 
10
10
  if (keyspace_name = args[:keyspace_name])
11
- cql << ' WHERE "keyspace" = ?'
11
+ cql << ' WHERE "keyspace_name" = ?'
12
12
  variables << keyspace_name
13
13
  end
14
14
 
@@ -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[:keyspace] = keyspace_name
18
+ where[:keyspace_name] = keyspace_name
19
19
  end
20
20
 
21
21
  if (column_family_name = args[:column_family_name])
22
- where[:columnfamily] = column_family_name
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
- with = {
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: 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 default_strategy_class
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
- if value.is_a?(Hash)
21
- value.each do |sub_key, sub_value|
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
 
@@ -1,16 +1,16 @@
1
1
  require 'forwardable'
2
- require 'cassandra-cql'
3
- require 'cassanity/executors/cassandra_cql'
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 CassandraCQL::Database being used.
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::CassandraCQL that will
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
- # options - The Hash of CassandraCQL::Database options.
26
- # thrift_options - The Hash of CassandraCQL::Database thrift client options.
27
- def initialize(servers = nil, options = {}, thrift_options = {})
28
- @servers = servers || '127.0.0.1:9160'
29
- @options = options.merge(cql_version: '3.0.0')
30
- @thrift_options = thrift_options.dup
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 = CassandraCQL::Database.new(@servers, @options, @thrift_options)
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 'cassandra-cql'
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 CassandraCql
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 CassandraCQL::Database connection instance.
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
- # CassandraCQL calls
104
+ # Cql::Client calls
104
105
  # (default: Cassanity::Instrumenters::RetryNTimes)
105
106
  #
106
107
  # Examples
107
108
  #
108
- # driver = CassandraCQL::Database.new('host', cql_version: '3.0.0')
109
- # Cassanity::Executors::CassandraCql.new(driver: driver)
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
- execute_arguments = generator.call(arguments)
162
- payload[:cql] = execute_arguments[0]
163
- payload[:cql_variables] = execute_arguments[1..-1]
164
- result = @retry_strategy.execute(payload) { @driver.execute(*execute_arguments) }
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
@@ -9,26 +9,20 @@ module Cassanity
9
9
  attr_reader :executor
10
10
 
11
11
  # Internal
12
- attr_reader :strategy_class
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
- # :strategy_class - The String strategy class name to use when
23
- # creating keyspace.
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
- @strategy_class = args[:strategy_class]
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
- # strategy_class: 'NetworkTopologyStrategy',
61
- # strategy_options: {
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({
@@ -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.fetch_hash do |row|
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.fetch_hash do |row|
10
+ result.each do |row|
11
11
  columns << Column.new({
12
- name: row['column'],
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.fetch_hash do |row|
10
+ result.each do |row|
11
11
  name = row['name'] || row['keyspace'] || row['keyspace_name']
12
12
  keyspaces << Keyspace.new({
13
13
  name: name,
@@ -4,11 +4,7 @@ module Cassanity
4
4
 
5
5
  # Internal: Turns result into Array of Hashes.
6
6
  def call(result, args = nil)
7
- rows = []
8
- result.fetch_hash do |row|
9
- rows << row
10
- end
11
- rows
7
+ result.map { |row| row }
12
8
  end
13
9
  end
14
10
  end
@@ -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 CassandraCQL driver. Call the retry method (overridden in your
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 CassandraCQL::Error::InvalidRequestException, Thrift::Exception => e
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
@@ -1,3 +1,3 @@
1
1
  module Cassanity
2
- VERSION = "0.5.1"
2
+ VERSION = "0.6.0.beta1"
3
3
  end
data/spec/helper.rb CHANGED
@@ -29,7 +29,5 @@ RSpec.configure do |config|
29
29
  config.include CassanityHelpers
30
30
  end
31
31
 
32
- host = ENV.fetch('CASSANITY_HOST', '127.0.0.1')
33
- port = ENV.fetch('CASSANITY_PORT', '9160')
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