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.
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