corm 0.0.25 → 0.0.26

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,2 +1,3 @@
1
1
  # encoding: utf-8
2
2
  require 'corm/model'
3
+ require 'corm/retry/policies'
@@ -1,9 +1,10 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Corm
4
- class TooManyKeysError < StandardError; end
5
- class UnknownKey < StandardError; end
6
- class MissingPartitionKey < StandardError; end
7
- class MissingClusteringKey < StandardError; end
8
- class UnknownClusteringKey < StandardError; end
4
+ class GenericError < StandardError; end
5
+ class TooManyKeysError < GenericError; end
6
+ class UnknownPrimaryKey < GenericError; end
7
+ class MissingPartitionKey < GenericError; end
8
+ class MissingClusteringKey < GenericError; end
9
+ class UnknownClusteringKey < GenericError; end
9
10
  end
@@ -1,7 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  require 'cassandra'
4
- require 'corm/enhancements'
5
4
  require 'corm/exceptions'
6
5
  require 'corm/validations'
7
6
  require 'multi_json'
@@ -11,7 +10,6 @@ require 'digest/md5'
11
10
  module Corm
12
11
  class Model
13
12
  include Enumerable
14
- extend Enhancements
15
13
  extend Validations
16
14
 
17
15
  @@cluster = nil
@@ -21,23 +19,15 @@ module Corm
21
19
  # give up...
22
20
  # If it fails `@@cluster` was nil and nil remains.
23
21
  def self.configure(opts = {})
24
- attempts_wrapper do
25
- @@cluster = Cassandra.cluster(opts)
26
- end
22
+ @@cluster = Cassandra.cluster(opts)
27
23
  end
28
24
 
29
25
  def self.cluster
30
26
  @@cluster
31
27
  end
32
28
 
33
- # Please, note the wrapper around the `session execute`.
34
- # This wrapper (implemented in the Enhancements module) will recover some
35
- # Cassandra::Error(s) retrying a couple of times.
36
- #
37
- # Note also that the <instance>#execute is just calling this Class#execute,
38
- # as well as count, find, get, drop, etc...
39
29
  def self.execute(*args)
40
- attempts_wrapper { session.execute(*args) }
30
+ session.execute(*args)
41
31
  end
42
32
 
43
33
  def self.field(name, type, pkey = false)
@@ -173,7 +163,7 @@ module Corm
173
163
  # The options hash support the ':limit' option to append at the statement;
174
164
  # the default is no limit.
175
165
  #
176
- # The 'key_values' parameter is an Hash where the keys are the "column
166
+ # The 'params' parameter is an Hash where the keys are the "column
177
167
  # names" and the values... are the values.
178
168
  #
179
169
  # If the keys passed as parameter are more than the defined by the table the
@@ -181,50 +171,27 @@ module Corm
181
171
  # Other exceptions are raised when the keys doesn't include all the
182
172
  # (required) partition_keys or the clustering keys doesn't are in the
183
173
  # defined order.
184
- def self.find(key_values = {}, query_options = {}, &block)
185
-
186
- raise ArgumentError unless key_values.is_a?(Hash)
187
- raise ArgumentError unless query_options.is_a?(Hash)
188
-
189
- unless key_values.empty?
190
- raise TooManyKeysError if there_are_too_many_keys_requested?(key_values)
191
- raise MissingPartitionKey if a_partition_key_is_missing?(key_values)
192
- raise UnknownClusteringKey if an_unknown_clustering_key_is_requested?(key_values)
193
- # raise UnknownKey if an_unknown_key_is_requested?(key_values)
194
- raise MissingClusteringKey if a_clustering_key_is_missing?(key_values)
195
- end
196
-
197
- return to_enum(:find, key_values, query_options) unless block_given?
198
-
199
- statement_find_key = Array(query_options.fetch(:statement_key, 'find')).flatten
200
- field_names = []
201
-
202
- key_values.each do |key, value|
203
-
204
- statement_find_key << key.to_s
205
- field_names << "#{key} = ?"
174
+ def self.find(params = {}, opts = {}, &block)
175
+ validate_query(params, opts)
176
+ return to_enum(:find, params, opts) unless block_given?
177
+ statement_key = Array(opts.fetch(:statement_key, 'find')).flatten
178
+ statement_key.concat(params.keys)
179
+ statement_key.concat(["_limit#{opts[:limit]}"]) if opts[:limit]
180
+ statement_key = statement_key.join('_')
181
+ fields = params.map { |key, _value| "#{key} = ?" }
182
+ if statements[statement_key].nil?
183
+ statement = select_statement_for(fields, opts[:limit])
184
+ statements[statement_key] = session.prepare(statement)
206
185
  end
207
-
208
- statement_find_key = statement_find_key.join('_')
209
- statement_find_key.concat("_limit#{query_options[:limit]}") if query_options[:limit]
210
-
211
- if statements[statement_find_key].nil?
212
- statement = self.the_select_statement_for(key_values, field_names, query_options[:limit])
213
- statements[statement_find_key] = session.prepare(statement)
214
- end
215
-
216
- execute(statements[statement_find_key], arguments: key_values).each do |cassandra_record_|
217
- block.call(new(_cassandra_record: cassandra_record_))
186
+ execute(statements[statement_key], arguments: params.values).each do |res|
187
+ block.call(new(_cassandra_record: res))
218
188
  end
219
189
  end
220
190
 
221
191
  def self.get(relations)
222
- query_options = {
223
- limit: 1,
224
- statement_key: 'get'
225
- }
226
- cassandra_record = self.find(relations, query_options).first
227
- return cassandra_record ? cassandra_record : nil
192
+ query_options = { limit: 1, statement_key: 'get' }
193
+ cassandra_record = find(relations, query_options).first
194
+ cassandra_record ? cassandra_record : nil
228
195
  end
229
196
 
230
197
  def self.keyspace(name = nil)
@@ -241,11 +208,9 @@ module Corm
241
208
  "{'class': 'SimpleStrategy', 'replication_factor': '1'}"
242
209
  durable_writes = opts[:durable_writes].nil? ? true : opts[:durable_writes]
243
210
  if_not_exists = opts[:if_not_exists] ? 'IF NOT EXISTS' : ''
244
- attempts_wrapper do
245
- cluster.connect.execute(
246
- "CREATE KEYSPACE #{if_not_exists} #{keyspace} WITH replication = #{replication} AND durable_writes = #{durable_writes};"
247
- )
248
- end
211
+ cluster.connect.execute(
212
+ "CREATE KEYSPACE #{if_not_exists} #{keyspace} WITH replication = #{replication} AND durable_writes = #{durable_writes};"
213
+ )
249
214
  end
250
215
 
251
216
  def self.primary_key(partition_key = nil, *cols)
@@ -257,15 +222,15 @@ module Corm
257
222
  end
258
223
 
259
224
  def self.primary_key_count
260
- self.primary_key.flatten.count
225
+ primary_key.flatten.count
261
226
  end
262
227
 
263
228
  def self.partition_key
264
- self.primary_key.first.map(&:to_sym)
229
+ primary_key.first.map(&:to_sym)
265
230
  end
266
231
 
267
232
  def self.clustering_key
268
- self.primary_key.count == 2 ? self.primary_key[1].flatten.map(&:to_sym) : []
233
+ primary_key.count == 2 ? primary_key[1].flatten.map(&:to_sym) : []
269
234
  end
270
235
 
271
236
  def self.properties(*args)
@@ -282,12 +247,10 @@ module Corm
282
247
  # This operation is wrapped by the retry policy (module Enhancements).
283
248
  def self.session
284
249
  unless class_variable_defined?(:@@session)
285
- attempts_wrapper do
286
- class_variable_set(
287
- :@@session,
288
- cluster.connect(keyspace)
289
- )
290
- end
250
+ class_variable_set(
251
+ :@@session,
252
+ cluster.connect(keyspace)
253
+ )
291
254
  end
292
255
  class_variable_get :@@session
293
256
  end
@@ -366,6 +329,20 @@ module Corm
366
329
 
367
330
  protected
368
331
 
332
+ ##
333
+ # Create and return the proper query to find the C* entries, given an array
334
+ # of keys.
335
+ #
336
+ # @param key_values An array of key names; can be empty, cannot be, in size, greater than the length of the model keys.
337
+ # @param field_names The "column names" for the `WHERE` clause.
338
+ def self.select_statement_for(fields, limit = nil)
339
+ statement = "SELECT * FROM #{keyspace}.#{table}"
340
+ statement += " WHERE #{fields.join(' AND ')}" unless fields.empty?
341
+ statement += " LIMIT #{limit.to_i}" if limit.to_i > 0
342
+ statement += ';'
343
+ statement
344
+ end
345
+
369
346
  def execute(*args)
370
347
  self.class.execute(*args)
371
348
  end
@@ -393,22 +370,5 @@ module Corm
393
370
  def table
394
371
  self.class.table
395
372
  end
396
-
397
- ##
398
- # Create and return the proper query to find the C* entries, given an array
399
- # of keys.
400
- #
401
- # @param key_values An array of key names; can be empty, cannot be, in size, greater than the length of the model keys.
402
- # @param field_names The "column names" for the `WHERE` clause.
403
- def self.the_select_statement_for(key_values, field_names, limit = nil)
404
- limit = "LIMIT #{limit.to_i}" if (limit && limit > 0)
405
- if key_values.empty?
406
- return "SELECT * FROM #{keyspace}.#{table} #{limit} ;"
407
- elsif key_values.count > self.primary_key_count
408
- raise Corm::TooManyKeysError
409
- else
410
- return "SELECT * FROM #{keyspace}.#{table} WHERE #{field_names.join(' AND ')} #{limit} ;"
411
- end
412
- end
413
373
  end
414
374
  end
@@ -0,0 +1 @@
1
+ require 'corm/retry/policies/default'
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+ require 'cassandra'
3
+
4
+ module Corm
5
+ module Retry
6
+ module Policies
7
+ class Default
8
+ include Cassandra::Retry::Policy
9
+
10
+ def read_timeout(_statement, consistency, _required, _received, retrieved, retries)
11
+ return reraise if retries >= 5
12
+ sleep(retries.to_f + Random.rand(0.0..1.0))
13
+ retrieved ? reraise : try_again(consistency)
14
+ end
15
+
16
+ def write_timeout(_statement, consistency, _type, _required, _received, retries)
17
+ return reraise if retries >= 5
18
+ sleep(retries.to_f + Random.rand(0.0..1.0))
19
+ try_again(consistency)
20
+ end
21
+
22
+ def unavailable(_statement, consistency, _required, _alive, retries)
23
+ return reraise if retries >= 5
24
+ sleep(retries.to_f + Random.rand(0.0..1.0))
25
+ try_again(consistency)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -2,55 +2,52 @@
2
2
 
3
3
  module Corm
4
4
  module Validations
5
-
6
- def there_are_too_many_keys_requested?(key_values)
7
- if key_values.keys.count > self.primary_key_count
8
- return "You defined more find keys than the primary keys of the table!"
9
- end
10
- false
5
+ def clustering_key_missing?(params)
6
+ return false if no_clustering_key?(params)
7
+ keys = clustering_key.map(&:to_sym) - params.keys.map(&:to_sym)
8
+ keys.empty? ? false : true
11
9
  end
12
10
 
13
- def an_unknown_key_is_requested?(key_values)
14
- unless (key_values.keys.map(&:to_sym) - self.primary_key.flatten).empty?
15
- return "You requested a key that it's not in the table primary key!"
16
- end
17
- false
11
+ def no_clustering_key?(params)
12
+ (params.keys.map(&:to_sym) - partition_key.flatten.map(&:to_sym)).empty?
18
13
  end
19
14
 
20
- def an_unknown_clustering_key_is_requested?(key_values)
21
- unless ((key_values.keys.map(&:to_sym) - self.partition_key.flatten) - self.clustering_key).empty?
22
- return "You requested some unsupported clustering keys!"
23
- end
24
- false
15
+ def partition_key_missing?(params)
16
+ keys = partition_key.map(&:to_sym) - params.keys.map(&:to_sym)
17
+ keys.empty? ? false : true
25
18
  end
26
19
 
27
- def a_partition_key_is_missing?(key_values)
28
- self.partition_key.each do |part_key|
29
- return "#{part_key} is required as partition key!" unless key_values.keys.map(&:to_sym).include?(part_key.to_sym)
30
- end
31
- false
20
+ def too_many_keys?(params)
21
+ params.keys.count > primary_key_count ? true : false
32
22
  end
33
23
 
34
- def a_clustering_key_is_missing?(key_values)
35
-
36
- # This exception mimic the following...
37
- # Class: <Cassandra::Errors::InvalidError>
38
- # Message: <"PRIMARY KEY column 'still_another_uuid_field' cannot be
39
- # restricted (preceding column 'another_uuid_field' is either not
40
- # restricted or by a non-EQ relation)"
41
- #
42
- # ... and TBH leaving this check to the Cassandra driver is still an
43
- # option.
44
- return false if self.clustering_key.empty?
45
- return false if no_clustering_key_requested?(key_values)
46
- self.clustering_key.each do |clust_key|
47
- return "#{clust_key} is required as clustering key! (Order matters)" unless key_values.keys.map(&:to_sym).include?(clust_key.to_sym)
48
- end
49
- false
24
+ def unknown_primary_key?(params)
25
+ (params.keys.map(&:to_sym) - primary_key.flatten.map(&:to_sym)).empty? ? false : true
50
26
  end
51
27
 
52
- def no_clustering_key_requested?(key_values)
53
- (key_values.keys.map(&:to_sym) - self.partition_key.flatten).empty?
28
+ def validate_query(params, opts)
29
+ if !params.is_a?(Hash)
30
+ error = ArgumentError
31
+ message = "'params' argument must be an hash: #{params}"
32
+ elsif !opts.is_a?(Hash)
33
+ error = ArgumentError
34
+ message = "'opts' argument must be an hash: #{opts}"
35
+ elsif too_many_keys?(params)
36
+ error = TooManyKeysError
37
+ message = "#{params.keys}"
38
+ elsif !params.empty? && partition_key_missing?(params)
39
+ error = MissingPartitionKey
40
+ message = "#{params.keys}"
41
+ elsif !params.empty? && clustering_key_missing?(params)
42
+ error = MissingClusteringKey
43
+ message = "#{params.keys}"
44
+ elsif !params.empty? && unknown_primary_key?(params)
45
+ error = UnknownPrimaryKey
46
+ message = "#{params.keys}"
47
+ else
48
+ return
49
+ end
50
+ fail(error, message, caller)
54
51
  end
55
52
  end
56
53
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: corm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.25
4
+ version: 0.0.26
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-06-25 00:00:00.000000000 Z
12
+ date: 2015-06-26 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: cassandra-driver
@@ -95,9 +95,10 @@ extensions: []
95
95
  extra_rdoc_files: []
96
96
  files:
97
97
  - lib/corm.rb
98
- - lib/corm/enhancements.rb
99
98
  - lib/corm/exceptions.rb
100
99
  - lib/corm/model.rb
100
+ - lib/corm/retry/policies.rb
101
+ - lib/corm/retry/policies/default.rb
101
102
  - lib/corm/validations.rb
102
103
  homepage: https://github.com/stefanofontanelli/corm
103
104
  licenses: []
@@ -113,7 +114,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
113
114
  version: '0'
114
115
  segments:
115
116
  - 0
116
- hash: -1990675893845593114
117
+ hash: -3769483612946445211
117
118
  required_rubygems_version: !ruby/object:Gem::Requirement
118
119
  none: false
119
120
  requirements:
@@ -122,7 +123,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
122
123
  version: '0'
123
124
  segments:
124
125
  - 0
125
- hash: -1990675893845593114
126
+ hash: -3769483612946445211
126
127
  requirements: []
127
128
  rubyforge_project:
128
129
  rubygems_version: 1.8.23.2
@@ -1,54 +0,0 @@
1
- # encoding: utf-8
2
-
3
- module Corm
4
- module Enhancements
5
-
6
- def enhancements_logger(logger = nil)
7
- Logger.new(STDOUT).tap { |l| l.level = Logger::INFO } unless logger
8
- end
9
-
10
- RESCUED_CASSANDRA_EXCEPTIONS = [
11
- ::Cassandra::Errors::ExecutionError,
12
- ::Cassandra::Errors::IOError,
13
- ::Cassandra::Errors::InternalError,
14
- ::Cassandra::Errors::NoHostsAvailable,
15
- ::Cassandra::Errors::ServerError,
16
- ::Cassandra::Errors::TimeoutError
17
- ]
18
-
19
- # Trying to rescue from a Cassandra::Error
20
- #
21
- # The relevant documentation is here (version 2.1.3):
22
- # https://datastax.github.io/ruby-driver/api/error/
23
- #
24
- # Saving from:
25
- #
26
- # - ::Cassandra::Errors::ExecutionError
27
- # - ::Cassandra::Errors::IOError
28
- # - ::Cassandra::Errors::InternalError
29
- # - ::Cassandra::Errors::NoHostsAvailable
30
- # - ::Cassandra::Errors::ServerError
31
- # - ::Cassandra::Errors::TimeoutError
32
- #
33
- # Ignoring:
34
- # - Errors::ClientError
35
- # - Errors::DecodingError
36
- # - Errors::EncodingError
37
- # - Errors::ValidationError
38
- #
39
- # A possible and maybe-good refactoring could be refine for the
40
- # network related issues.
41
- def attempts_wrapper(attempts = 3, &block)
42
- (1..attempts).each do |i|
43
- begin
44
- return block.call() if block_given?
45
- rescue *RESCUED_CASSANDRA_EXCEPTIONS => e
46
- sleep_for = i * Random.rand(1.5..2.5)
47
- enhancements_logger.error { "(#{i}/#{attempts} attempts) Bad fail! Retry in #{sleep_for} seconds to recover #{e.class.name}: #{e.message}" }
48
- sleep(sleep_for)
49
- end
50
- end
51
- nil
52
- end
53
- end
54
- end