corm 0.0.25 → 0.0.26

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