cequel 1.1.2 → 1.2.0
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.
- checksums.yaml +4 -4
- data/Appraisals +3 -1
- data/CHANGELOG.md +6 -0
- data/Gemfile +5 -5
- data/Gemfile.lock +17 -68
- data/README.md +11 -3
- data/lib/cequel/record.rb +3 -3
- data/lib/cequel/record/finders.rb +102 -0
- data/lib/cequel/record/properties.rb +8 -0
- data/lib/cequel/record/record_set.rb +91 -29
- data/lib/cequel/version.rb +1 -1
- data/spec/examples/record/finders_spec.rb +177 -0
- data/spec/examples/record/record_set_spec.rb +98 -19
- data/spec/support/helpers.rb +2 -2
- metadata +24 -59
- data/lib/cequel/record/secondary_indexes.rb +0 -68
- data/spec/examples/record/secondary_index_spec.rb +0 -46
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8151f255eac647140677b154e3d786f1ed817a26
|
|
4
|
+
data.tar.gz: e7b3214a86a25d8c6d98aa68e9d5e4a992731ab8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 95d754925f448719477b3981b89f9be29d23ed6209708bc8675ad839f08da3283c98fc3c9cbdca2afc9eaf62019c46ca7783b0ded8d7da7d446e3fed5a95c87e
|
|
7
|
+
data.tar.gz: 60dca007fbbb8c678eb5f5219c90824b1bc80e1411a16a246d14f664498c693902eed64aef349cda3762a095399feba39ed5ee703ac41810005bf869388b7ba8
|
data/Appraisals
CHANGED
|
@@ -4,12 +4,14 @@ end
|
|
|
4
4
|
|
|
5
5
|
appraise "rails-3.2" do
|
|
6
6
|
gem "activesupport", "~> 3.2.0"
|
|
7
|
+
gem "tzinfo", "~> 0.3"
|
|
7
8
|
end
|
|
8
9
|
|
|
9
10
|
appraise "rails-3.1" do
|
|
10
11
|
gem "activesupport", "~> 3.1.0"
|
|
12
|
+
gem "tzinfo", "~> 0.3"
|
|
11
13
|
end
|
|
12
14
|
|
|
13
15
|
appraise "thrift" do
|
|
14
|
-
gem "cassandra-cql"
|
|
16
|
+
gem "cassandra-cql", "~> 1.2"
|
|
15
17
|
end
|
data/CHANGELOG.md
CHANGED
data/Gemfile
CHANGED
|
@@ -3,11 +3,11 @@ source 'https://rubygems.org'
|
|
|
3
3
|
gemspec
|
|
4
4
|
|
|
5
5
|
group :debug do
|
|
6
|
-
gem 'debugger', :platforms => :mri_19
|
|
7
|
-
gem 'byebug', :platforms => [:mri_20, :mri_21]
|
|
8
|
-
gem 'pry'
|
|
6
|
+
gem 'debugger', '~> 1.6', :platforms => :mri_19
|
|
7
|
+
gem 'byebug', '~> 2.7', :platforms => [:mri_20, :mri_21]
|
|
8
|
+
gem 'pry', '~> 0.9'
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
gem 'racc', :platforms => :rbx
|
|
11
|
+
gem 'racc', '~> 1.4', :platforms => :rbx
|
|
12
12
|
gem 'rubysl', '~> 2.0', :platforms => :rbx
|
|
13
|
-
gem 'psych', :platforms => :rbx
|
|
13
|
+
gem 'psych', '~> 2.0', :platforms => :rbx
|
data/Gemfile.lock
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
cequel (1.
|
|
5
|
-
activemodel (>= 3.1)
|
|
6
|
-
cql-rb
|
|
4
|
+
cequel (1.2.0)
|
|
5
|
+
activemodel (>= 3.1, < 5.0)
|
|
6
|
+
cql-rb (~> 1.2)
|
|
7
7
|
|
|
8
8
|
GEM
|
|
9
9
|
remote: https://rubygems.org/
|
|
@@ -17,14 +17,12 @@ GEM
|
|
|
17
17
|
multi_json (~> 1.3)
|
|
18
18
|
thread_safe (~> 0.1)
|
|
19
19
|
tzinfo (~> 0.3.37)
|
|
20
|
-
addressable (2.3.5)
|
|
21
20
|
appraisal (0.5.2)
|
|
22
21
|
bundler
|
|
23
22
|
rake
|
|
24
23
|
ast (1.1.0)
|
|
25
|
-
atomic (1.1.
|
|
26
|
-
atomic (1.1.
|
|
27
|
-
backports (3.6.0)
|
|
24
|
+
atomic (1.1.16)
|
|
25
|
+
atomic (1.1.16-java)
|
|
28
26
|
builder (3.1.4)
|
|
29
27
|
byebug (2.7.0)
|
|
30
28
|
columnize (~> 0.3)
|
|
@@ -39,40 +37,14 @@ GEM
|
|
|
39
37
|
debugger-linecache (1.2.0)
|
|
40
38
|
debugger-ruby_core_source (1.3.2)
|
|
41
39
|
diff-lcs (1.2.5)
|
|
42
|
-
ethon (0.6.3)
|
|
43
|
-
ffi (>= 1.3.0)
|
|
44
|
-
mime-types (~> 1.18)
|
|
45
|
-
faraday (0.8.9)
|
|
46
|
-
multipart-post (~> 1.2.0)
|
|
47
|
-
faraday_middleware (0.9.0)
|
|
48
|
-
faraday (>= 0.7.4, < 0.9)
|
|
49
|
-
ffi (1.9.3)
|
|
50
40
|
ffi (1.9.3-java)
|
|
51
41
|
ffi2-generators (0.1.1)
|
|
52
|
-
gh (0.13.2)
|
|
53
|
-
addressable
|
|
54
|
-
backports
|
|
55
|
-
faraday (~> 0.8)
|
|
56
|
-
multi_json (~> 1.0)
|
|
57
|
-
net-http-persistent (>= 2.7)
|
|
58
|
-
net-http-pipeline
|
|
59
|
-
hashr (0.0.22)
|
|
60
|
-
highline (1.6.21)
|
|
61
42
|
i18n (0.6.9)
|
|
62
43
|
json (1.8.1)
|
|
63
44
|
json (1.8.1-java)
|
|
64
|
-
launchy (2.4.2)
|
|
65
|
-
addressable (~> 2.3)
|
|
66
|
-
launchy (2.4.2-java)
|
|
67
|
-
addressable (~> 2.3)
|
|
68
|
-
spoon (~> 0.0.1)
|
|
69
45
|
method_source (0.8.2)
|
|
70
|
-
mime-types (1.25.1)
|
|
71
46
|
minitest (4.7.5)
|
|
72
|
-
multi_json (1.9.
|
|
73
|
-
multipart-post (1.2.0)
|
|
74
|
-
net-http-persistent (2.9.4)
|
|
75
|
-
net-http-pipeline (1.0.1)
|
|
47
|
+
multi_json (1.9.2)
|
|
76
48
|
parser (2.1.7)
|
|
77
49
|
ast (~> 1.1)
|
|
78
50
|
slop (~> 3.4, >= 3.4.5)
|
|
@@ -87,8 +59,6 @@ GEM
|
|
|
87
59
|
slop (~> 3.4)
|
|
88
60
|
spoon (~> 0.0)
|
|
89
61
|
psych (2.0.4)
|
|
90
|
-
pusher-client (0.4.0)
|
|
91
|
-
websocket (~> 1.0.0)
|
|
92
62
|
racc (1.4.11)
|
|
93
63
|
rainbow (2.0.0)
|
|
94
64
|
rake (10.1.1)
|
|
@@ -100,7 +70,7 @@ GEM
|
|
|
100
70
|
rspec-expectations (2.14.5)
|
|
101
71
|
diff-lcs (>= 1.1.3, < 2.0)
|
|
102
72
|
rspec-mocks (2.14.6)
|
|
103
|
-
rubocop (0.19.
|
|
73
|
+
rubocop (0.19.1)
|
|
104
74
|
json (>= 1.7.7, < 2)
|
|
105
75
|
parser (~> 2.1.7)
|
|
106
76
|
powerpack (~> 0.0.6)
|
|
@@ -309,33 +279,15 @@ GEM
|
|
|
309
279
|
rubysl-xmlrpc (2.0.0)
|
|
310
280
|
rubysl-yaml (2.0.4)
|
|
311
281
|
rubysl-zlib (2.0.1)
|
|
312
|
-
safe_yaml (0.9.7)
|
|
313
282
|
slop (3.5.0)
|
|
314
283
|
spoon (0.0.4)
|
|
315
284
|
ffi
|
|
316
|
-
thread_safe (0.
|
|
285
|
+
thread_safe (0.3.1)
|
|
317
286
|
atomic (>= 1.1.7, < 2)
|
|
318
|
-
thread_safe (0.
|
|
287
|
+
thread_safe (0.3.1-java)
|
|
319
288
|
atomic (>= 1.1.7, < 2)
|
|
320
|
-
travis (1.6.8)
|
|
321
|
-
addressable (~> 2.3)
|
|
322
|
-
backports
|
|
323
|
-
faraday (~> 0.8.7)
|
|
324
|
-
faraday_middleware (~> 0.9)
|
|
325
|
-
gh (~> 0.13)
|
|
326
|
-
highline (~> 1.6)
|
|
327
|
-
launchy (~> 2.1)
|
|
328
|
-
pry (~> 0.9)
|
|
329
|
-
pusher-client (~> 0.4)
|
|
330
|
-
typhoeus (~> 0.6)
|
|
331
|
-
travis-lint (1.8.0)
|
|
332
|
-
hashr (~> 0.0.22)
|
|
333
|
-
safe_yaml (~> 0.9.0)
|
|
334
|
-
typhoeus (0.6.7)
|
|
335
|
-
ethon (~> 0.6.2)
|
|
336
289
|
tzinfo (0.3.39)
|
|
337
|
-
|
|
338
|
-
yard (0.8.7.3)
|
|
290
|
+
yard (0.8.7.4)
|
|
339
291
|
|
|
340
292
|
PLATFORMS
|
|
341
293
|
java
|
|
@@ -343,17 +295,14 @@ PLATFORMS
|
|
|
343
295
|
|
|
344
296
|
DEPENDENCIES
|
|
345
297
|
appraisal (~> 0.5)
|
|
346
|
-
byebug
|
|
298
|
+
byebug (~> 2.7)
|
|
347
299
|
cequel!
|
|
348
|
-
debugger
|
|
349
|
-
pry
|
|
350
|
-
psych
|
|
351
|
-
racc
|
|
352
|
-
rake
|
|
300
|
+
debugger (~> 1.6)
|
|
301
|
+
pry (~> 0.9)
|
|
302
|
+
psych (~> 2.0)
|
|
303
|
+
racc (~> 1.4)
|
|
304
|
+
rake (~> 10.1)
|
|
353
305
|
rspec (~> 2.0)
|
|
354
|
-
rubocop
|
|
306
|
+
rubocop (~> 0.19)
|
|
355
307
|
rubysl (~> 2.0)
|
|
356
|
-
travis
|
|
357
|
-
travis-lint
|
|
358
|
-
tzinfo
|
|
359
308
|
yard (~> 0.6)
|
data/README.md
CHANGED
|
@@ -155,6 +155,17 @@ the `[]` operator, these methods operate on the first unscoped primary key:
|
|
|
155
155
|
Post['bigdata'].after(last_id) # scopes posts with blog_subdomain="bigdata" and id > last_id
|
|
156
156
|
```
|
|
157
157
|
|
|
158
|
+
You can also use `where` to scope to primary key columns, but a primary key
|
|
159
|
+
column can only be scoped if all the columns that come before it are also
|
|
160
|
+
scoped:
|
|
161
|
+
|
|
162
|
+
```ruby
|
|
163
|
+
Post.where(blog_subdomain: 'bigdata') # this is fine
|
|
164
|
+
Post.where(blog_subdomain: 'bigdata', permalink: 'cassandra') # also fine
|
|
165
|
+
Post.where(blog_subdomain: 'bigdata').where(permalink: 'cassandra') # also fine
|
|
166
|
+
Post.where(permalink: 'cassandra') # bad: can't use permalink without blog_subdomain
|
|
167
|
+
```
|
|
168
|
+
|
|
158
169
|
Note that record sets always load records in batches; Cassandra does not support
|
|
159
170
|
result sets of unbounded size. This process is transparent to you but you'll see
|
|
160
171
|
multiple queries in your logs if you're iterating over a huge result set.
|
|
@@ -308,9 +319,6 @@ You can also call the `where` method directly on record sets:
|
|
|
308
319
|
Post.where(:author_id, id)
|
|
309
320
|
```
|
|
310
321
|
|
|
311
|
-
Note that `where` is only for secondary indexed columns; use `[]` to scope
|
|
312
|
-
record sets by primary keys.
|
|
313
|
-
|
|
314
322
|
### Consistency tuning ###
|
|
315
323
|
|
|
316
324
|
Cassandra supports [tunable
|
data/lib/cequel/record.rb
CHANGED
|
@@ -13,7 +13,7 @@ require 'cequel/record/data_set_builder'
|
|
|
13
13
|
require 'cequel/record/bound'
|
|
14
14
|
require 'cequel/record/lazy_record_collection'
|
|
15
15
|
require 'cequel/record/scoped'
|
|
16
|
-
require 'cequel/record/
|
|
16
|
+
require 'cequel/record/finders'
|
|
17
17
|
require 'cequel/record/associations'
|
|
18
18
|
require 'cequel/record/association_collection'
|
|
19
19
|
require 'cequel/record/belongs_to_association'
|
|
@@ -64,11 +64,11 @@ module Cequel
|
|
|
64
64
|
#
|
|
65
65
|
# @see Properties Defining properties
|
|
66
66
|
# @see Collection Collection columns
|
|
67
|
-
# @see SecondaryIndexes Defining secondary indexes
|
|
68
67
|
# @see Associations Defining associations between records
|
|
69
68
|
# @see Persistence Creating, updating, and destroying records
|
|
70
69
|
# @see BulkWrites Updating and destroying records in bulk
|
|
71
70
|
# @see RecordSet Loading records from the database
|
|
71
|
+
# @see Finders Magic finder methods
|
|
72
72
|
# @see MassAssignment Mass-assignment protection and strong attributes
|
|
73
73
|
# @see Callbacks Lifecycle hooks
|
|
74
74
|
# @see Validations
|
|
@@ -84,7 +84,7 @@ module Cequel
|
|
|
84
84
|
include Persistence
|
|
85
85
|
include Associations
|
|
86
86
|
include Scoped
|
|
87
|
-
extend
|
|
87
|
+
extend Finders
|
|
88
88
|
include MassAssignment
|
|
89
89
|
include Callbacks
|
|
90
90
|
include Validations
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
module Cequel
|
|
2
|
+
module Record
|
|
3
|
+
#
|
|
4
|
+
# Cequel provides finder methods to construct scopes for looking up records
|
|
5
|
+
# by primary key or secondary indexed columns.
|
|
6
|
+
#
|
|
7
|
+
# @example Example model class
|
|
8
|
+
# class Post
|
|
9
|
+
# key :blog_subdomain, :text
|
|
10
|
+
# key :id, :timeuuid, auto: true
|
|
11
|
+
# column :title, :text
|
|
12
|
+
# column :body, :text
|
|
13
|
+
# column :author_id, :uuid, index: true # this column has a secondary
|
|
14
|
+
# # index
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# @example Using some but not all primary key columns
|
|
18
|
+
# # return an Array of all posts with given subdomain (greedy load)
|
|
19
|
+
# Post.find_all_by_blog_subdomain(subdomain)
|
|
20
|
+
#
|
|
21
|
+
# # return a {RecordSet} of all posts with the given subdomain (lazy
|
|
22
|
+
# # load)
|
|
23
|
+
# Post.with_subdomain(subdomain)
|
|
24
|
+
#
|
|
25
|
+
# @example Using all primary key columns
|
|
26
|
+
# # return the first post with the given subdomain and id, or nil if none
|
|
27
|
+
# Post.find_by_blog_subdomain_and_id(subdomain, id)
|
|
28
|
+
#
|
|
29
|
+
# # return a record set to the post with the given subdomain and id
|
|
30
|
+
# # (one element array if exists, empty array otherwise)
|
|
31
|
+
# Post.with_blog_subdomain_and_id(subdomain, id)
|
|
32
|
+
#
|
|
33
|
+
# @example Chaining
|
|
34
|
+
# # return the first post with the given subdomain and id, or nil if none
|
|
35
|
+
# # Note that find_by_id can only be called on a scope that already has a
|
|
36
|
+
# # filter value for blog_subdomain
|
|
37
|
+
# Post.with_blog_subdomain(subdomain).find_by_id(id)
|
|
38
|
+
#
|
|
39
|
+
# @example Using a secondary index
|
|
40
|
+
# # return the first record with the author_id
|
|
41
|
+
# Post.find_by_author_id(id)
|
|
42
|
+
#
|
|
43
|
+
# # return an Array of all records with the author_id
|
|
44
|
+
# Post.find_all_by_author_id(id)
|
|
45
|
+
#
|
|
46
|
+
# # return a RecordSet scoped to the author_id
|
|
47
|
+
# Post.with_author_id(id)
|
|
48
|
+
#
|
|
49
|
+
# @since 1.2.0
|
|
50
|
+
#
|
|
51
|
+
module Finders
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def key(*)
|
|
55
|
+
if key_columns.any?
|
|
56
|
+
def_key_finders('find_all_by', '.entries')
|
|
57
|
+
undef_key_finders('find_by')
|
|
58
|
+
end
|
|
59
|
+
super
|
|
60
|
+
def_key_finders('find_by', '.first')
|
|
61
|
+
def_key_finders('with')
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def column(name, type, options = {})
|
|
65
|
+
super
|
|
66
|
+
if options[:index]
|
|
67
|
+
def_finder('with', [name])
|
|
68
|
+
def_finder('find_by', [name], '.first')
|
|
69
|
+
def_finder('find_all_by', [name], '.entries')
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def def_key_finders(method_prefix, scope_operation = '')
|
|
74
|
+
def_finder(method_prefix, key_column_names, scope_operation)
|
|
75
|
+
def_finder(method_prefix, key_column_names.last(1), scope_operation)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def def_finder(method_prefix, column_names, scope_operation = '')
|
|
79
|
+
arg_names = column_names.join(', ')
|
|
80
|
+
method_suffix = finder_method_suffix(column_names)
|
|
81
|
+
column_filter_expr = column_names
|
|
82
|
+
.map { |name| "#{name}: #{name}" }.join(', ')
|
|
83
|
+
|
|
84
|
+
singleton_class.module_eval(<<-RUBY, __FILE__, __LINE__+1)
|
|
85
|
+
def #{method_prefix}_#{method_suffix}(#{arg_names})
|
|
86
|
+
where(#{column_filter_expr})#{scope_operation}
|
|
87
|
+
end
|
|
88
|
+
RUBY
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def undef_key_finders(method_prefix)
|
|
92
|
+
method_suffix = finder_method_suffix(key_column_names)
|
|
93
|
+
method_name = "#{method_prefix}_#{method_suffix}"
|
|
94
|
+
singleton_class.module_eval { undef_method(method_name) }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def finder_method_suffix(column_names)
|
|
98
|
+
column_names.join('_and_')
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -116,8 +116,16 @@ module Cequel
|
|
|
116
116
|
# @param options [Options] options for the column
|
|
117
117
|
# @option options [Object,Proc] :default a default value for the
|
|
118
118
|
# column, or a proc that returns a default value for the column
|
|
119
|
+
# @option options [Boolean,Symbol] :index create a secondary index on
|
|
120
|
+
# this column
|
|
119
121
|
# @return [void]
|
|
120
122
|
#
|
|
123
|
+
# @note Secondary indexes are not nearly as flexible as primary keys:
|
|
124
|
+
# you cannot query for multiple values or for ranges of values. You
|
|
125
|
+
# also cannot combine a secondary index restriction with a primary
|
|
126
|
+
# key restriction in the same query, nor can you combine more than
|
|
127
|
+
# one secondary index restriction in the same query.
|
|
128
|
+
#
|
|
121
129
|
def column(name, type, options = {})
|
|
122
130
|
def_accessors(name)
|
|
123
131
|
set_attribute_default(name, options[:default])
|
|
@@ -181,38 +181,38 @@ module Cequel
|
|
|
181
181
|
# Filter the record set to records containing a given value in an indexed
|
|
182
182
|
# column
|
|
183
183
|
#
|
|
184
|
-
# @
|
|
185
|
-
#
|
|
186
|
-
#
|
|
187
|
-
#
|
|
188
|
-
#
|
|
189
|
-
#
|
|
190
|
-
#
|
|
191
|
-
#
|
|
192
|
-
#
|
|
193
|
-
#
|
|
184
|
+
# @overload where(column_name, value)
|
|
185
|
+
# @param column_name [Symbol] column for filter
|
|
186
|
+
# @param value value to match in given column
|
|
187
|
+
# @return [RecordSet] record set with filter applied
|
|
188
|
+
# @deprecated
|
|
189
|
+
#
|
|
190
|
+
# @overload where(column_values)
|
|
191
|
+
# @param column_values [Hash] map of key column names to values
|
|
192
|
+
# @return [RecordSet] record set with filter applied
|
|
193
|
+
#
|
|
194
|
+
# @raise [IllegalQuery] if applying filter would generate an impossible
|
|
195
|
+
# query
|
|
196
|
+
# @raise [ArgumentError] if the specified column is not a column that
|
|
197
|
+
# can be filtered on
|
|
198
|
+
#
|
|
199
|
+
# @note Filtering on a primary key requires also filtering on all prior
|
|
200
|
+
# primary keys
|
|
194
201
|
# @note Only one secondary index filter can be used in a given query
|
|
195
202
|
# @note Secondary index filters cannot be mixed with primary key filters
|
|
196
203
|
#
|
|
197
|
-
def where(
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
"No column #{column_name} configured for #{target_class.name}"
|
|
206
|
-
end
|
|
207
|
-
unless column.data_column?
|
|
208
|
-
fail ArgumentError,
|
|
209
|
-
"Use the `at` method to restrict scope by primary key"
|
|
210
|
-
end
|
|
211
|
-
unless column.indexed?
|
|
204
|
+
def where(*args)
|
|
205
|
+
if args.length == 1
|
|
206
|
+
column_filters = args.first.symbolize_keys
|
|
207
|
+
elsif args.length == 2
|
|
208
|
+
warn "where(column_name, value) is deprecated. Use " \
|
|
209
|
+
"where(column_name => value) instead"
|
|
210
|
+
column_filters = {args.first.to_sym => args.second}
|
|
211
|
+
else
|
|
212
212
|
fail ArgumentError,
|
|
213
|
-
"
|
|
213
|
+
"wrong number of arguments (#{args.length} for 1..2)"
|
|
214
214
|
end
|
|
215
|
-
|
|
215
|
+
filter_columns(column_filters)
|
|
216
216
|
end
|
|
217
217
|
|
|
218
218
|
#
|
|
@@ -639,6 +639,11 @@ module Cequel
|
|
|
639
639
|
entries == other.to_a
|
|
640
640
|
end
|
|
641
641
|
|
|
642
|
+
# @private
|
|
643
|
+
def to_ary
|
|
644
|
+
entries
|
|
645
|
+
end
|
|
646
|
+
|
|
642
647
|
protected
|
|
643
648
|
|
|
644
649
|
attr_reader :attributes
|
|
@@ -683,6 +688,57 @@ module Cequel
|
|
|
683
688
|
end
|
|
684
689
|
end
|
|
685
690
|
|
|
691
|
+
def filter_columns(column_values)
|
|
692
|
+
return self if column_values.empty?
|
|
693
|
+
|
|
694
|
+
if column_values.key?(next_unscoped_key_name)
|
|
695
|
+
filter_primary_key(column_values.delete(next_unscoped_key_name))
|
|
696
|
+
else
|
|
697
|
+
filter_secondary_index(*column_values.shift)
|
|
698
|
+
end.filter_columns(column_values)
|
|
699
|
+
end
|
|
700
|
+
|
|
701
|
+
def filter_primary_key(value)
|
|
702
|
+
if scoped_indexed_column
|
|
703
|
+
fail IllegalQuery,
|
|
704
|
+
"Can't filter by both primary key and secondary index"
|
|
705
|
+
end
|
|
706
|
+
if value.is_a?(Range)
|
|
707
|
+
self.in(value)
|
|
708
|
+
else
|
|
709
|
+
scoped { |attributes| attributes[:scoped_key_values] << value }
|
|
710
|
+
end
|
|
711
|
+
end
|
|
712
|
+
|
|
713
|
+
def filter_secondary_index(column_name, value)
|
|
714
|
+
column = target_class.reflect_on_column(column_name)
|
|
715
|
+
if column.nil?
|
|
716
|
+
fail ArgumentError,
|
|
717
|
+
"No column #{column_name} configured for #{target_class.name}"
|
|
718
|
+
end
|
|
719
|
+
if column.key?
|
|
720
|
+
missing_column_names = unscoped_key_names.take_while do |key_name|
|
|
721
|
+
key_name != column_name
|
|
722
|
+
end
|
|
723
|
+
fail IllegalQuery,
|
|
724
|
+
"Can't scope key column #{column_name} without also scoping " \
|
|
725
|
+
"#{missing_column_names.join(', ')}"
|
|
726
|
+
end
|
|
727
|
+
if scoped_key_values.any?
|
|
728
|
+
fail IllegalQuery,
|
|
729
|
+
"Can't filter by both primary key and secondary index"
|
|
730
|
+
end
|
|
731
|
+
if scoped_indexed_column
|
|
732
|
+
fail IllegalQuery,
|
|
733
|
+
"Can't scope by more than one indexed column in the same query"
|
|
734
|
+
end
|
|
735
|
+
unless column.indexed?
|
|
736
|
+
fail ArgumentError,
|
|
737
|
+
"Can't scope by non-indexed column #{column_name}"
|
|
738
|
+
end
|
|
739
|
+
scoped(scoped_indexed_column: {column_name => column.cast(value)})
|
|
740
|
+
end
|
|
741
|
+
|
|
686
742
|
def scoped_key_names
|
|
687
743
|
scoped_key_columns.map { |column| column.name }
|
|
688
744
|
end
|
|
@@ -707,6 +763,14 @@ module Cequel
|
|
|
707
763
|
range_key_column.name
|
|
708
764
|
end
|
|
709
765
|
|
|
766
|
+
def next_unscoped_key_column
|
|
767
|
+
unscoped_key_columns.first
|
|
768
|
+
end
|
|
769
|
+
|
|
770
|
+
def next_unscoped_key_name
|
|
771
|
+
next_unscoped_key_column.name
|
|
772
|
+
end
|
|
773
|
+
|
|
710
774
|
def next_range_key_column
|
|
711
775
|
unscoped_key_columns.second
|
|
712
776
|
end
|
|
@@ -736,8 +800,6 @@ module Cequel
|
|
|
736
800
|
end
|
|
737
801
|
|
|
738
802
|
def next_unscoped_key_column_valid_for_in_query?
|
|
739
|
-
next_unscoped_key_column = unscoped_key_columns.first
|
|
740
|
-
|
|
741
803
|
next_unscoped_key_column == partition_key_columns.last ||
|
|
742
804
|
next_unscoped_key_column == clustering_columns.last
|
|
743
805
|
end
|
data/lib/cequel/version.rb
CHANGED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
require_relative 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Cequel::Record::Finders do
|
|
4
|
+
model :Blog do
|
|
5
|
+
key :subdomain, :text
|
|
6
|
+
column :name, :text
|
|
7
|
+
column :description, :text
|
|
8
|
+
column :owner_id, :uuid
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
model :User do
|
|
12
|
+
key :login, :text
|
|
13
|
+
column :name, :text
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
model :Post do
|
|
17
|
+
key :blog_subdomain, :text
|
|
18
|
+
key :permalink, :text
|
|
19
|
+
column :title, :text
|
|
20
|
+
column :body, :text
|
|
21
|
+
column :author_id, :uuid, index: true
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
let :blogs do
|
|
25
|
+
cequel.batch do
|
|
26
|
+
5.times.map do |i|
|
|
27
|
+
Blog.create!(subdomain: "cassandra#{i}", name: 'Cassandra')
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
let(:author_ids) { Array.new(2) { Cequel.uuid }}
|
|
33
|
+
|
|
34
|
+
let :cassandra_posts do
|
|
35
|
+
cequel.batch do
|
|
36
|
+
5.times.map do |i|
|
|
37
|
+
Post.create!(
|
|
38
|
+
blog_subdomain: 'cassandra',
|
|
39
|
+
permalink: "cassandra#{i}",
|
|
40
|
+
author_id: author_ids[i%2]
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
let :postgres_posts do
|
|
47
|
+
cequel.batch do
|
|
48
|
+
5.times.map do |i|
|
|
49
|
+
Post.create!(blog_subdomain: 'postgres', permalink: "postgres#{i}")
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
let(:posts) { cassandra_posts + postgres_posts }
|
|
55
|
+
|
|
56
|
+
context 'simple primary key' do
|
|
57
|
+
|
|
58
|
+
let!(:blog) { blogs.first }
|
|
59
|
+
|
|
60
|
+
describe '#find_by_*' do
|
|
61
|
+
it 'should return matching record' do
|
|
62
|
+
expect(Blog.find_by_subdomain('cassandra0')).to eq(blog)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'should return nil if no record matches' do
|
|
66
|
+
expect(Blog.find_by_subdomain('bogus')).to be_nil
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it 'should respond to method before it is called' do
|
|
70
|
+
expect(User).to be_respond_to(:find_by_login)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it 'should raise error on wrong name' do
|
|
74
|
+
expect { Blog.find_by_bogus('bogus') }.to raise_error(NoMethodError)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it 'should not respond to wrong name' do
|
|
78
|
+
expect(User).to_not be_respond_to(:find_by_bogus)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
describe '#find_all_by_*' do
|
|
83
|
+
it 'should raise error if called' do
|
|
84
|
+
expect { Blog.find_all_by_subdomain('outoftime') }
|
|
85
|
+
.to raise_error(NoMethodError)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it 'should not respond' do
|
|
89
|
+
expect(User).not_to be_respond_to(:find_all_by_login)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
context 'compound primary key' do
|
|
95
|
+
|
|
96
|
+
let!(:post) { posts.first }
|
|
97
|
+
|
|
98
|
+
describe '#find_all_by_*' do
|
|
99
|
+
it 'should return all records matching key prefix' do
|
|
100
|
+
expect(Post.find_all_by_blog_subdomain('cassandra'))
|
|
101
|
+
.to eq(cassandra_posts)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it 'should greedily load records' do
|
|
105
|
+
records = Post.find_all_by_blog_subdomain('cassandra')
|
|
106
|
+
disallow_queries!
|
|
107
|
+
expect(records).to eq(cassandra_posts)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it 'should return empty array if nothing matches' do
|
|
111
|
+
expect(Post.find_all_by_blog_subdomain('bogus')).to eq([])
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
it 'should not exist for all keys' do
|
|
115
|
+
expect { Post.find_all_by_blog_subdomain_and_permalink('f', 'b') }
|
|
116
|
+
.to raise_error(NoMethodError)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
describe '#find_by_*' do
|
|
121
|
+
it 'should return record matching all keys' do
|
|
122
|
+
expect(Post.find_by_blog_subdomain_and_permalink('cassandra',
|
|
123
|
+
'cassandra0'))
|
|
124
|
+
.to eq(cassandra_posts.first)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
it 'should not exist for key prefix' do
|
|
128
|
+
expect { Post.find_by_blog_subdomain('foo') }
|
|
129
|
+
.to raise_error(NoMethodError)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
it 'should allow lower-order key if chained' do
|
|
133
|
+
expect(Post.where(blog_subdomain: 'cassandra')
|
|
134
|
+
.find_by_permalink('cassandra0')).to eq(cassandra_posts.first)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
describe '#with_*' do
|
|
139
|
+
it 'should return record matching all keys' do
|
|
140
|
+
expect(Post.with_blog_subdomain_and_permalink('cassandra',
|
|
141
|
+
'cassandra0'))
|
|
142
|
+
.to eq(cassandra_posts.first(1))
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
it 'should return all records matching key prefix' do
|
|
146
|
+
expect(Post.with_blog_subdomain('cassandra'))
|
|
147
|
+
.to eq(cassandra_posts)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
context 'secondary index' do
|
|
153
|
+
before { cassandra_posts }
|
|
154
|
+
|
|
155
|
+
it 'should expose scope to query by secondary index' do
|
|
156
|
+
expect(Post.with_author_id(author_ids.first))
|
|
157
|
+
.to match_array(cassandra_posts.values_at(0, 2, 4))
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
it 'should expose method to retrieve first result by secondary index' do
|
|
161
|
+
expect(Post.find_by_author_id(author_ids.first))
|
|
162
|
+
.to eq(cassandra_posts.first)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
it 'should expose method to eagerly retrieve all results by secondary index' do
|
|
166
|
+
posts = Post.find_all_by_author_id(author_ids.first)
|
|
167
|
+
disallow_queries!
|
|
168
|
+
expect(posts).to match_array(cassandra_posts.values_at(0, 2, 4))
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
it 'should not expose methods for non-indexed columns' do
|
|
172
|
+
[:find_by_title, :find_all_by_title, :with_title].each do |method|
|
|
173
|
+
expect(Post).to_not respond_to(method)
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
@@ -585,35 +585,114 @@ describe Cequel::Record::RecordSet do
|
|
|
585
585
|
end
|
|
586
586
|
|
|
587
587
|
describe '#where' do
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
588
|
+
context 'simple primary key' do
|
|
589
|
+
let(:records) { blogs }
|
|
590
|
+
|
|
591
|
+
it 'should correctly query for simple primary key with two arguments' do
|
|
592
|
+
expect(Blog.where(:subdomain, 'blog-0'))
|
|
593
|
+
.to eq(blogs.first(1))
|
|
594
|
+
end
|
|
595
|
+
|
|
596
|
+
it 'should correctly query for simple primary key with hash argument' do
|
|
597
|
+
expect(Blog.where(subdomain: 'blog-0'))
|
|
598
|
+
.to eq(blogs.first(1))
|
|
599
|
+
end
|
|
591
600
|
end
|
|
592
601
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
602
|
+
context 'compound primary key' do
|
|
603
|
+
it 'should correctly query for first primary key column' do
|
|
604
|
+
expect(Post.where(blog_subdomain: 'cassandra'))
|
|
605
|
+
.to eq(cassandra_posts)
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
it 'should perform IN query when passed multiple values' do
|
|
609
|
+
expect(Post.where(blog_subdomain: %w(cassandra postgres)))
|
|
610
|
+
.to match_array(cassandra_posts + postgres_posts)
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
it 'should correctly query for both primary key columns' do
|
|
614
|
+
expect(Post.where(blog_subdomain: 'cassandra', permalink: 'cequel0'))
|
|
615
|
+
.to eq(cassandra_posts.first(1))
|
|
616
|
+
end
|
|
617
|
+
|
|
618
|
+
it 'should correctly query for both primary key columns chained' do
|
|
619
|
+
expect(Post.where(blog_subdomain: 'cassandra')
|
|
620
|
+
.where(permalink: 'cequel0'))
|
|
621
|
+
.to eq(cassandra_posts.first(1))
|
|
622
|
+
end
|
|
623
|
+
|
|
624
|
+
it 'should perform range query when passed range' do
|
|
625
|
+
expect(Post.where(blog_subdomain: %w(cassandra),
|
|
626
|
+
permalink: 'cequel0'..'cequel2'))
|
|
627
|
+
.to eq(cassandra_posts.first(3))
|
|
628
|
+
end
|
|
629
|
+
|
|
630
|
+
it 'should raise error if lower-order primary key specified without higher' do
|
|
631
|
+
expect { Post.where(permalink: 'cequel0').first }
|
|
632
|
+
.to raise_error(Cequel::Record::IllegalQuery)
|
|
633
|
+
end
|
|
596
634
|
end
|
|
597
635
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
636
|
+
context 'secondary indexed column' do
|
|
637
|
+
it 'should query for secondary indexed columns with two arguments' do
|
|
638
|
+
Post.where(:author_id, uuids.first).map(&:permalink).
|
|
639
|
+
should == %w(cequel0 cequel2 cequel4)
|
|
640
|
+
end
|
|
641
|
+
|
|
642
|
+
it 'should query for secondary indexed columns with hash argument' do
|
|
643
|
+
Post.where(author_id: uuids.first).map(&:permalink).
|
|
644
|
+
should == %w(cequel0 cequel2 cequel4)
|
|
645
|
+
end
|
|
646
|
+
|
|
647
|
+
it 'should not allow multiple columns in the arguments' do
|
|
648
|
+
expect { Post.where(author_id: uuids.first, author_name: 'Mat Brown') }
|
|
649
|
+
.to raise_error(Cequel::Record::IllegalQuery)
|
|
650
|
+
end
|
|
651
|
+
|
|
652
|
+
it 'should not allow chaining of multiple columns' do
|
|
653
|
+
expect { Post.where(:author_id, uuids.first).
|
|
654
|
+
where(:author_name, 'Mat Brown') }.
|
|
655
|
+
to raise_error(Cequel::Record::IllegalQuery)
|
|
656
|
+
end
|
|
657
|
+
|
|
658
|
+
it 'should cast argument for column' do
|
|
659
|
+
Post.where(:author_id, uuids.first.to_s).map(&:permalink).
|
|
660
|
+
should == %w(cequel0 cequel2 cequel4)
|
|
661
|
+
end
|
|
601
662
|
end
|
|
602
663
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
664
|
+
context 'mixing keys and secondary-indexed columns' do
|
|
665
|
+
it 'should not allow mixture in hash argument' do
|
|
666
|
+
expect { Post.where(blog_subdomain: 'cassandra',
|
|
667
|
+
author_id: uuids.first) }
|
|
668
|
+
.to raise_error(Cequel::Record::IllegalQuery)
|
|
669
|
+
end
|
|
670
|
+
|
|
671
|
+
it 'should not allow mixture in chain with primary first' do
|
|
672
|
+
expect { Post.where(blog_subdomain: 'cassandra')
|
|
673
|
+
.where(author_id: uuids.first) }
|
|
674
|
+
.to raise_error(Cequel::Record::IllegalQuery)
|
|
675
|
+
end
|
|
676
|
+
|
|
677
|
+
it 'should not allow mixture in chain with secondary first' do
|
|
678
|
+
expect { Post.where(author_id: uuids.first)
|
|
679
|
+
.where(blog_subdomain: 'cassandra') }
|
|
680
|
+
.to raise_error(Cequel::Record::IllegalQuery)
|
|
681
|
+
end
|
|
606
682
|
end
|
|
607
683
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
684
|
+
context 'nonexistent column' do
|
|
685
|
+
it 'should raise ArgumentError if column is not recognized' do
|
|
686
|
+
expect { Post.where(:bogus, 'Business') }.
|
|
687
|
+
to raise_error(ArgumentError)
|
|
688
|
+
end
|
|
611
689
|
end
|
|
612
690
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
where(:
|
|
616
|
-
|
|
691
|
+
context 'non-indexed column' do
|
|
692
|
+
it 'should raise ArgumentError if column is not indexed' do
|
|
693
|
+
expect { Post.where(:title, 'Cequel 0') }.
|
|
694
|
+
to raise_error(ArgumentError)
|
|
695
|
+
end
|
|
617
696
|
end
|
|
618
697
|
end
|
|
619
698
|
|
data/spec/support/helpers.rb
CHANGED
|
@@ -108,11 +108,11 @@ module Cequel
|
|
|
108
108
|
end
|
|
109
109
|
|
|
110
110
|
def max_statements!(number)
|
|
111
|
-
cequel.should_receive(:execute).at_most(number).times.and_call_original
|
|
111
|
+
cequel.client.should_receive(:execute).at_most(number).times.and_call_original
|
|
112
112
|
end
|
|
113
113
|
|
|
114
114
|
def disallow_queries!
|
|
115
|
-
cequel.should_not_receive(:execute)
|
|
115
|
+
cequel.client.should_not_receive(:execute)
|
|
116
116
|
end
|
|
117
117
|
|
|
118
118
|
def expect_query_with_consistency(matcher, consistency)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cequel
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mat Brown
|
|
@@ -12,10 +12,11 @@ authors:
|
|
|
12
12
|
- Peter Williams
|
|
13
13
|
- Kenneth Hoffman
|
|
14
14
|
- Antti Tapio
|
|
15
|
+
- Ilya Bazylchuk
|
|
15
16
|
autorequire:
|
|
16
17
|
bindir: bin
|
|
17
18
|
cert_chain: []
|
|
18
|
-
date: 2014-03-
|
|
19
|
+
date: 2014-03-24 00:00:00.000000000 Z
|
|
19
20
|
dependencies:
|
|
20
21
|
- !ruby/object:Gem::Dependency
|
|
21
22
|
name: activemodel
|
|
@@ -24,6 +25,9 @@ dependencies:
|
|
|
24
25
|
- - ">="
|
|
25
26
|
- !ruby/object:Gem::Version
|
|
26
27
|
version: '3.1'
|
|
28
|
+
- - "<"
|
|
29
|
+
- !ruby/object:Gem::Version
|
|
30
|
+
version: '5.0'
|
|
27
31
|
type: :runtime
|
|
28
32
|
prerelease: false
|
|
29
33
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -31,20 +35,23 @@ dependencies:
|
|
|
31
35
|
- - ">="
|
|
32
36
|
- !ruby/object:Gem::Version
|
|
33
37
|
version: '3.1'
|
|
38
|
+
- - "<"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '5.0'
|
|
34
41
|
- !ruby/object:Gem::Dependency
|
|
35
42
|
name: cql-rb
|
|
36
43
|
requirement: !ruby/object:Gem::Requirement
|
|
37
44
|
requirements:
|
|
38
|
-
- - "
|
|
45
|
+
- - "~>"
|
|
39
46
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: '
|
|
47
|
+
version: '1.2'
|
|
41
48
|
type: :runtime
|
|
42
49
|
prerelease: false
|
|
43
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
44
51
|
requirements:
|
|
45
|
-
- - "
|
|
52
|
+
- - "~>"
|
|
46
53
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: '
|
|
54
|
+
version: '1.2'
|
|
48
55
|
- !ruby/object:Gem::Dependency
|
|
49
56
|
name: appraisal
|
|
50
57
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -91,72 +98,30 @@ dependencies:
|
|
|
91
98
|
name: rake
|
|
92
99
|
requirement: !ruby/object:Gem::Requirement
|
|
93
100
|
requirements:
|
|
94
|
-
- - "
|
|
101
|
+
- - "~>"
|
|
95
102
|
- !ruby/object:Gem::Version
|
|
96
|
-
version: '
|
|
103
|
+
version: '10.1'
|
|
97
104
|
type: :development
|
|
98
105
|
prerelease: false
|
|
99
106
|
version_requirements: !ruby/object:Gem::Requirement
|
|
100
107
|
requirements:
|
|
101
|
-
- - "
|
|
108
|
+
- - "~>"
|
|
102
109
|
- !ruby/object:Gem::Version
|
|
103
|
-
version: '
|
|
110
|
+
version: '10.1'
|
|
104
111
|
- !ruby/object:Gem::Dependency
|
|
105
112
|
name: rubocop
|
|
106
113
|
requirement: !ruby/object:Gem::Requirement
|
|
107
114
|
requirements:
|
|
108
|
-
- - "
|
|
109
|
-
- !ruby/object:Gem::Version
|
|
110
|
-
version: '0'
|
|
111
|
-
type: :development
|
|
112
|
-
prerelease: false
|
|
113
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
114
|
-
requirements:
|
|
115
|
-
- - ">="
|
|
116
|
-
- !ruby/object:Gem::Version
|
|
117
|
-
version: '0'
|
|
118
|
-
- !ruby/object:Gem::Dependency
|
|
119
|
-
name: travis
|
|
120
|
-
requirement: !ruby/object:Gem::Requirement
|
|
121
|
-
requirements:
|
|
122
|
-
- - ">="
|
|
123
|
-
- !ruby/object:Gem::Version
|
|
124
|
-
version: '0'
|
|
125
|
-
type: :development
|
|
126
|
-
prerelease: false
|
|
127
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
128
|
-
requirements:
|
|
129
|
-
- - ">="
|
|
130
|
-
- !ruby/object:Gem::Version
|
|
131
|
-
version: '0'
|
|
132
|
-
- !ruby/object:Gem::Dependency
|
|
133
|
-
name: travis-lint
|
|
134
|
-
requirement: !ruby/object:Gem::Requirement
|
|
135
|
-
requirements:
|
|
136
|
-
- - ">="
|
|
137
|
-
- !ruby/object:Gem::Version
|
|
138
|
-
version: '0'
|
|
139
|
-
type: :development
|
|
140
|
-
prerelease: false
|
|
141
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
142
|
-
requirements:
|
|
143
|
-
- - ">="
|
|
144
|
-
- !ruby/object:Gem::Version
|
|
145
|
-
version: '0'
|
|
146
|
-
- !ruby/object:Gem::Dependency
|
|
147
|
-
name: tzinfo
|
|
148
|
-
requirement: !ruby/object:Gem::Requirement
|
|
149
|
-
requirements:
|
|
150
|
-
- - ">="
|
|
115
|
+
- - "~>"
|
|
151
116
|
- !ruby/object:Gem::Version
|
|
152
|
-
version: '0'
|
|
117
|
+
version: '0.19'
|
|
153
118
|
type: :development
|
|
154
119
|
prerelease: false
|
|
155
120
|
version_requirements: !ruby/object:Gem::Requirement
|
|
156
121
|
requirements:
|
|
157
|
-
- - "
|
|
122
|
+
- - "~>"
|
|
158
123
|
- !ruby/object:Gem::Version
|
|
159
|
-
version: '0'
|
|
124
|
+
version: '0.19'
|
|
160
125
|
description: |
|
|
161
126
|
Cequel is an ActiveRecord-like domain model layer for Cassandra that exposes
|
|
162
127
|
the robust data modeling capabilities of CQL3, including parent-child
|
|
@@ -209,6 +174,7 @@ files:
|
|
|
209
174
|
- lib/cequel/record/data_set_builder.rb
|
|
210
175
|
- lib/cequel/record/dirty.rb
|
|
211
176
|
- lib/cequel/record/errors.rb
|
|
177
|
+
- lib/cequel/record/finders.rb
|
|
212
178
|
- lib/cequel/record/has_many_association.rb
|
|
213
179
|
- lib/cequel/record/lazy_record_collection.rb
|
|
214
180
|
- lib/cequel/record/mass_assignment.rb
|
|
@@ -219,7 +185,6 @@ files:
|
|
|
219
185
|
- lib/cequel/record/record_set.rb
|
|
220
186
|
- lib/cequel/record/schema.rb
|
|
221
187
|
- lib/cequel/record/scoped.rb
|
|
222
|
-
- lib/cequel/record/secondary_indexes.rb
|
|
223
188
|
- lib/cequel/record/tasks.rb
|
|
224
189
|
- lib/cequel/record/validations.rb
|
|
225
190
|
- lib/cequel/schema.rb
|
|
@@ -244,6 +209,7 @@ files:
|
|
|
244
209
|
- spec/examples/record/associations_spec.rb
|
|
245
210
|
- spec/examples/record/callbacks_spec.rb
|
|
246
211
|
- spec/examples/record/dirty_spec.rb
|
|
212
|
+
- spec/examples/record/finders_spec.rb
|
|
247
213
|
- spec/examples/record/list_spec.rb
|
|
248
214
|
- spec/examples/record/map_spec.rb
|
|
249
215
|
- spec/examples/record/mass_assignment_spec.rb
|
|
@@ -253,7 +219,6 @@ files:
|
|
|
253
219
|
- spec/examples/record/record_set_spec.rb
|
|
254
220
|
- spec/examples/record/schema_spec.rb
|
|
255
221
|
- spec/examples/record/scoped_spec.rb
|
|
256
|
-
- spec/examples/record/secondary_index_spec.rb
|
|
257
222
|
- spec/examples/record/serialization_spec.rb
|
|
258
223
|
- spec/examples/record/set_spec.rb
|
|
259
224
|
- spec/examples/record/spec_helper.rb
|
|
@@ -301,6 +266,7 @@ test_files:
|
|
|
301
266
|
- spec/examples/record/associations_spec.rb
|
|
302
267
|
- spec/examples/record/callbacks_spec.rb
|
|
303
268
|
- spec/examples/record/dirty_spec.rb
|
|
269
|
+
- spec/examples/record/finders_spec.rb
|
|
304
270
|
- spec/examples/record/list_spec.rb
|
|
305
271
|
- spec/examples/record/map_spec.rb
|
|
306
272
|
- spec/examples/record/mass_assignment_spec.rb
|
|
@@ -310,7 +276,6 @@ test_files:
|
|
|
310
276
|
- spec/examples/record/record_set_spec.rb
|
|
311
277
|
- spec/examples/record/schema_spec.rb
|
|
312
278
|
- spec/examples/record/scoped_spec.rb
|
|
313
|
-
- spec/examples/record/secondary_index_spec.rb
|
|
314
279
|
- spec/examples/record/serialization_spec.rb
|
|
315
280
|
- spec/examples/record/set_spec.rb
|
|
316
281
|
- spec/examples/record/spec_helper.rb
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
# -*- encoding : utf-8 -*-
|
|
2
|
-
module Cequel
|
|
3
|
-
module Record
|
|
4
|
-
#
|
|
5
|
-
# Data columns may be given secondary indexes. This allows you to query the
|
|
6
|
-
# table for records where the indexed column contains a single value.
|
|
7
|
-
# Secondary indexes are not nearly as flexible as primary keys: you cannot
|
|
8
|
-
# query for multiple values or for ranges of values. You also cannot
|
|
9
|
-
# combine a secondary index restriction with a primary key restriction in
|
|
10
|
-
# the same query, nor can you combine more than one secondary index
|
|
11
|
-
# restrictions in the same query.
|
|
12
|
-
#
|
|
13
|
-
# If a column is given a secondary index, magic finder methods `find_by_*`,
|
|
14
|
-
# `find_all_by_*`, and `with_*` are added to the class singleton. See below
|
|
15
|
-
# for an example.
|
|
16
|
-
#
|
|
17
|
-
# Secondary indexes are fairly expensive for Cassandra and should only be
|
|
18
|
-
# defined where needed.
|
|
19
|
-
#
|
|
20
|
-
# @example Defining a secondary index
|
|
21
|
-
# class Post
|
|
22
|
-
# belongs_to :blog
|
|
23
|
-
# key :id, :timeuuid, auto: true
|
|
24
|
-
# column :title, :text
|
|
25
|
-
# column :body, :text
|
|
26
|
-
# column :author_id, :uuid, index: true # this column has a secondary
|
|
27
|
-
# # index
|
|
28
|
-
# end
|
|
29
|
-
#
|
|
30
|
-
# @example Using a secondary index
|
|
31
|
-
# # return the first record with the author_id
|
|
32
|
-
# Post.find_by_author_id(id)
|
|
33
|
-
#
|
|
34
|
-
# # return an Array of all records with the author_id
|
|
35
|
-
# Post.find_all_by_author_id(id)
|
|
36
|
-
#
|
|
37
|
-
# # return a RecordSet scoped to the author_id
|
|
38
|
-
# Post.with_author_id(id)
|
|
39
|
-
#
|
|
40
|
-
# # same as with_author_id
|
|
41
|
-
# Post.where(:author_id, id)
|
|
42
|
-
#
|
|
43
|
-
# @since 1.0.0
|
|
44
|
-
#
|
|
45
|
-
module SecondaryIndexes
|
|
46
|
-
# @private
|
|
47
|
-
def column(name, type, options = {})
|
|
48
|
-
super
|
|
49
|
-
name = name.to_sym
|
|
50
|
-
if options[:index]
|
|
51
|
-
instance_eval <<-RUBY, __FILE__, __LINE__+1
|
|
52
|
-
def with_#{name}(value)
|
|
53
|
-
all.where(#{name.inspect}, value)
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def find_by_#{name}(value)
|
|
57
|
-
with_#{name}(value).first
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def find_all_by_#{name}(value)
|
|
61
|
-
with_#{name}(value).to_a
|
|
62
|
-
end
|
|
63
|
-
RUBY
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
end
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
# -*- encoding : utf-8 -*-
|
|
2
|
-
require_relative 'spec_helper'
|
|
3
|
-
|
|
4
|
-
describe Cequel::Record::SecondaryIndexes do
|
|
5
|
-
model :Post do
|
|
6
|
-
key :blog_subdomain, :text
|
|
7
|
-
key :permalink, :text
|
|
8
|
-
column :title, :text
|
|
9
|
-
column :author_id, :uuid, :index => true
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
let(:uuids) { Array.new(2) { Cequel.uuid }}
|
|
13
|
-
|
|
14
|
-
let!(:posts) do
|
|
15
|
-
3.times.map do |i|
|
|
16
|
-
Post.create! do |post|
|
|
17
|
-
post.blog_subdomain = 'bigdata'
|
|
18
|
-
post.permalink = "cequel#{i}"
|
|
19
|
-
post.title = "Cequel #{i}"
|
|
20
|
-
post.author_id = uuids[i%2]
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
it 'should create secondary index in schema' do
|
|
26
|
-
cequel.schema.read_table(:posts).data_columns.
|
|
27
|
-
find { |column| column.name == :author_id }.index_name.
|
|
28
|
-
should be
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
it 'should expose scope to query by secondary index' do
|
|
32
|
-
Post.with_author_id(uuids.first).map(&:permalink).
|
|
33
|
-
should == %w(cequel0 cequel2)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
it 'should expose method to retrieve first result by secondary index' do
|
|
37
|
-
Post.find_by_author_id(uuids.first).should == posts.first
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
it 'should expose method to eagerly retrieve all results by secondary index' do
|
|
41
|
-
posts = Post.find_all_by_author_id(uuids.first)
|
|
42
|
-
disallow_queries!
|
|
43
|
-
posts.map(&:permalink).should == %w(cequel0 cequel2)
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
end
|