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.
- data/lib/corm.rb +1 -0
- data/lib/corm/exceptions.rb +6 -5
- data/lib/corm/model.rb +43 -83
- data/lib/corm/retry/policies.rb +1 -0
- data/lib/corm/retry/policies/default.rb +30 -0
- data/lib/corm/validations.rb +36 -39
- metadata +6 -5
- data/lib/corm/enhancements.rb +0 -54
data/lib/corm.rb
CHANGED
data/lib/corm/exceptions.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
module Corm
|
4
|
-
class
|
5
|
-
class
|
6
|
-
class
|
7
|
-
class
|
8
|
-
class
|
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
|
data/lib/corm/model.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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 '
|
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(
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
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
|
-
|
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
|
-
|
224
|
-
|
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
|
-
|
245
|
-
|
246
|
-
|
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
|
-
|
225
|
+
primary_key.flatten.count
|
261
226
|
end
|
262
227
|
|
263
228
|
def self.partition_key
|
264
|
-
|
229
|
+
primary_key.first.map(&:to_sym)
|
265
230
|
end
|
266
231
|
|
267
232
|
def self.clustering_key
|
268
|
-
|
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
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
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
|
data/lib/corm/validations.rb
CHANGED
@@ -2,55 +2,52 @@
|
|
2
2
|
|
3
3
|
module Corm
|
4
4
|
module Validations
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
14
|
-
|
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
|
21
|
-
|
22
|
-
|
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
|
28
|
-
|
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
|
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
|
53
|
-
|
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.
|
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-
|
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: -
|
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: -
|
126
|
+
hash: -3769483612946445211
|
126
127
|
requirements: []
|
127
128
|
rubyforge_project:
|
128
129
|
rubygems_version: 1.8.23.2
|
data/lib/corm/enhancements.rb
DELETED
@@ -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
|