cassie 1.0.0.beta.1 → 1.0.0.beta.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/cassie/queries/README.md +74 -7
- data/lib/cassie/queries/statement/batches.rb +56 -0
- data/lib/cassie/queries/statement/callbacks.rb +1 -1
- data/lib/cassie/queries/statement/execution.rb +2 -0
- data/lib/cassie/queries/statement/fetching.rb +10 -8
- data/lib/cassie/queries/statement/loading.rb +1 -1
- data/lib/cassie/queries/statement.rb +2 -0
- data/lib/cassie/testing/fake/query.rb +1 -0
- data/lib/cassie/testing/fake/result.rb +8 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: df9c9472bd40950a8fb5cae45a95ab79661276f6
|
4
|
+
data.tar.gz: 79a9cad9af8d1410c73937f4c8ff52da630f40b5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1a331744722854ba71c48f432b41a1e19c54ee3ba15f40cd44922ba0be832d591ed31895faf8c371cb22b952bbf2e6aec1303c86b390bb08e2ae01bdf56887e6
|
7
|
+
data.tar.gz: 59939300bfc14cb906015dee1fa13dafa72050011853f33a82cb1a11bd6bb42b293905529750bd9dd56e4e4bc0bed2e39806b3439b7b6ecb68a94a0e7a9251fe
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# Cassie Queries
|
2
2
|
|
3
|
-
`cassie` query classes provide query interface that is
|
3
|
+
`cassie` query classes aim to provide a query interface that is
|
4
4
|
|
5
5
|
* Easy to use
|
6
6
|
* Easy to understand (and thus maintain)
|
@@ -9,7 +9,7 @@
|
|
9
9
|
|
10
10
|
### Usage
|
11
11
|
|
12
|
-
|
12
|
+
You might expect to see class methods allowing queries to be built like such:
|
13
13
|
|
14
14
|
```
|
15
15
|
Cassie.insert(:users_by_username,
|
@@ -22,7 +22,7 @@ Queries defined on the fly like this tend to create debt for an application in t
|
|
22
22
|
* resist documentation
|
23
23
|
* resist refactoring
|
24
24
|
|
25
|
-
Your application queries represent behavior, `cassie` queries are
|
25
|
+
Your application queries represent distinct application behavior, `cassie` queries are designed to help you create query classes that are reusable, testable and maintainable (so you can sleep better at night).
|
26
26
|
|
27
27
|
```ruby
|
28
28
|
# Some PORO user model
|
@@ -200,8 +200,74 @@ task :interesting_task do
|
|
200
200
|
|
201
201
|
InterestingWorker.new.perform
|
202
202
|
end
|
203
|
+
```
|
204
|
+
|
205
|
+
#### Finders
|
206
|
+
|
207
|
+
To avoid confusion with ruby `Enumerable#find` and Rails' specific `find` functionality, Cassie::Query opts to use `fetch` and explict `fetch_first` or `fetch_first!` methods.
|
208
|
+
|
209
|
+
##### `fetch`
|
210
|
+
|
211
|
+
Executes the query; returns array of results.
|
212
|
+
|
213
|
+
```
|
214
|
+
UsersByResourceQuery.new.fetch(resource: some_resource)
|
215
|
+
=> [#<User id=:123, username=:eprothro>, #<User id=:456, username=:tenderlove>]
|
216
|
+
```
|
217
|
+
|
218
|
+
##### `fetch_first` and `fetch_first!`
|
219
|
+
|
220
|
+
Executes the query, temporarily limited to 1 result; returns a single result. Bang version raises if no result is found.
|
221
|
+
|
222
|
+
```
|
223
|
+
UsersByUsernameQuery.new.fetch_first(username: "eprothro").username
|
224
|
+
=> "eprothro"
|
225
|
+
```
|
226
|
+
|
227
|
+
```
|
228
|
+
UsersByUsernameQuery.new.fetch_first(username: "active record").username
|
229
|
+
Cassie::Queries::RecordNotFound: CQL row does not exist
|
230
|
+
```
|
231
|
+
|
232
|
+
##### Batching
|
233
|
+
|
234
|
+
Similar to [Rails Batching](http://guides.rubyonrails.org/v4.2/active_record_querying.html#retrieving-multiple-objects-in-batches), Cassie allows efficient batching of `SELECT` queries.
|
235
|
+
|
236
|
+
###### `fetch_each`
|
237
|
+
```
|
238
|
+
UsersQuery.new.fetch_each do |user|
|
239
|
+
# hey look, only 1000 loaded at a time!
|
240
|
+
end
|
241
|
+
```
|
242
|
+
|
243
|
+
```
|
244
|
+
UsersQuery.new.fetch_each(batch_size: 500) do |user|
|
245
|
+
# hey look, only 500 loaded at a time!
|
246
|
+
end
|
247
|
+
```
|
248
|
+
|
249
|
+
```
|
250
|
+
UsersQuery.new.fetch_each.with_index do |user, index|
|
251
|
+
# hey look, Enumerator chaining!
|
252
|
+
end
|
253
|
+
```
|
254
|
+
###### `fetch_in_batches`
|
255
|
+
```
|
256
|
+
UsersQuery.new.fetch_in_batches do |users_array|
|
257
|
+
# hey look, only 1000 loaded at a time!
|
258
|
+
end
|
259
|
+
```
|
203
260
|
|
261
|
+
```
|
262
|
+
UsersQuery.new.fetch_in_batches(batch_size: 500) do |users_array|
|
263
|
+
# hey look, only 500 loaded at a time!
|
264
|
+
end
|
265
|
+
```
|
204
266
|
|
267
|
+
```
|
268
|
+
UsersQuery.new.fetch_in_batches.with_index do |group, index|
|
269
|
+
# hey look, Enumerator chaining!
|
270
|
+
end
|
205
271
|
```
|
206
272
|
|
207
273
|
#### Object Mapping
|
@@ -211,11 +277,11 @@ For Selection Queries, resources are returned as structs by default for manipula
|
|
211
277
|
UsersByUsernameQuery.new.fetch(username: "eprothro")
|
212
278
|
=> [#<Struct id=:123, username=:eprothro>]
|
213
279
|
|
214
|
-
UsersByUsernameQuery.new.
|
280
|
+
UsersByUsernameQuery.new.fetch_first(username: "eprothro").username
|
215
281
|
=> "eprothro"
|
216
282
|
```
|
217
283
|
|
218
|
-
|
284
|
+
Most application will want to override `build_resource` to construct more useful domain objects
|
219
285
|
|
220
286
|
```
|
221
287
|
class UsersByUsernameQuery < Cassie::Query
|
@@ -235,7 +301,7 @@ UsersByUsernameQuery.new.find(username: "eprothro")
|
|
235
301
|
=> #<User:0x007fedec219cd8 @id=123, @username="eprothro">
|
236
302
|
```
|
237
303
|
|
238
|
-
For Data Modification Queries (`insert`, `update`, `delete`), mapping binding values from
|
304
|
+
For Data Modification Queries (`insert`, `update`, `delete`), mapping binding values from a domain object is supported.
|
239
305
|
|
240
306
|
```ruby
|
241
307
|
class UpdateUserQuery < Cassandra::Query
|
@@ -252,7 +318,7 @@ class UpdateUserQuery < Cassandra::Query
|
|
252
318
|
map_from :user
|
253
319
|
```
|
254
320
|
|
255
|
-
|
321
|
+
This allows a domain object to be passed to the modification method, where binding values will be retrieved from the object
|
256
322
|
|
257
323
|
```ruby
|
258
324
|
user
|
@@ -264,6 +330,7 @@ UpdateUserQuery.new.update(user)
|
|
264
330
|
(1.2ms) UPDATE users_by_id (phone, email, address, username) VALUES (?, ?, ?, ?) WHERE id = ?; [["+15555555555", "etp@example.com", nil, "etp", 6539]]
|
265
331
|
</b></pre>
|
266
332
|
|
333
|
+
|
267
334
|
#### Cursored paging
|
268
335
|
|
269
336
|
Read about [cursored pagination](https://www.google.com/webhp?q=cursored%20paging#safe=off&q=cursor+paging) if unfamiliar with concept and how it optimizes paging through frequently updated data sets and I/O bandwidth.
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require_relative 'relation'
|
2
|
+
require_relative 'loading'
|
3
|
+
require_relative 'batches'
|
4
|
+
|
5
|
+
module Cassie::Queries::Statement
|
6
|
+
module Batches
|
7
|
+
extend ::ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
attr_reader :paging_state
|
11
|
+
attr_reader :stateless_page_size
|
12
|
+
end
|
13
|
+
|
14
|
+
def fetch_each
|
15
|
+
end
|
16
|
+
|
17
|
+
# Yields each batch of records that was found by the options as an array.
|
18
|
+
#
|
19
|
+
# If you do not provide a block to find_in_batches, it will return an Enumerator for chaining with other methods.
|
20
|
+
#
|
21
|
+
# query.fetch_in_batches do |records|
|
22
|
+
# puts "max score in group: #{records.max{ |a, b| a.score <=> b.score }}"
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# "max score in group: 26"
|
26
|
+
def fetch_in_batches(opts={})
|
27
|
+
return to_enum(:fetch_in_batches, opts) unless block_given?
|
28
|
+
opts[:batch_size] ||= 1000
|
29
|
+
|
30
|
+
# set cassandra internal
|
31
|
+
# stateless page size (independent from limit)
|
32
|
+
@stateless_page_size = opts[:batch_size]
|
33
|
+
@paging_state = nil
|
34
|
+
done = false
|
35
|
+
|
36
|
+
# use Cassandra internal paging
|
37
|
+
# but keep result within this query object
|
38
|
+
loop do
|
39
|
+
raise page_size_changed_error(opts[:batch_size]) if opts[:batch_size] != self.stateless_page_size
|
40
|
+
batch = fetch
|
41
|
+
|
42
|
+
@paging_state = result.paging_state
|
43
|
+
break if done
|
44
|
+
done = true if result.last_page?
|
45
|
+
|
46
|
+
yield batch
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def page_size_changed_error(original_size)
|
53
|
+
Cassie::Queries::Statement::Invalid.new("Page size is no longer valid. It was #{original_size} when the batch was started, and is now #{self.page_size}. Continuing would cause unexpected results.")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -20,6 +20,8 @@ module Cassie::Queries::Statement
|
|
20
20
|
#TODO: rework consistency module to be more
|
21
21
|
# abstract implementation for all execution options
|
22
22
|
opts[:consistency] = consistency if consistency
|
23
|
+
opts[:paging_state] = paging_state if respond_to?(:paging_state) && paging_state
|
24
|
+
opts[:page_size] = stateless_page_size if respond_to?(:stateless_page_size) && stateless_page_size
|
23
25
|
end
|
24
26
|
end
|
25
27
|
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require_relative 'relation'
|
2
2
|
require_relative 'loading'
|
3
|
+
require_relative 'batches'
|
3
4
|
|
4
5
|
class Cassie::Queries::RecordNotFound < StandardError; end
|
5
6
|
|
@@ -9,6 +10,7 @@ module Cassie::Queries::Statement
|
|
9
10
|
|
10
11
|
included do
|
11
12
|
include Loading
|
13
|
+
include Batches
|
12
14
|
end
|
13
15
|
|
14
16
|
# Returns array of rows or empty array
|
@@ -27,16 +29,16 @@ module Cassie::Queries::Statement
|
|
27
29
|
|
28
30
|
# Returns first result or nil
|
29
31
|
#
|
30
|
-
# query.
|
32
|
+
# query.fetch_first(id: 1)
|
31
33
|
# => {id: 1, name: 'eprothro'}
|
32
34
|
#
|
33
|
-
# query.
|
35
|
+
# query.fetch_first(id: 2)
|
34
36
|
# => nil
|
35
|
-
def
|
37
|
+
def fetch_first(args={})
|
36
38
|
old_limit = defined?(@limit) ? @limit : nil
|
37
39
|
self.limit = 1
|
38
40
|
|
39
|
-
fetch.first
|
41
|
+
fetch(args).first
|
40
42
|
ensure
|
41
43
|
if old_limit
|
42
44
|
@limit = old_limit
|
@@ -47,13 +49,13 @@ module Cassie::Queries::Statement
|
|
47
49
|
|
48
50
|
# Returns first result or raises RecordNotFound
|
49
51
|
#
|
50
|
-
# query.
|
52
|
+
# query.fetch_first!(id: 1)
|
51
53
|
# => {id: 1, name: 'eprothro'}
|
52
54
|
#
|
53
|
-
# query.
|
55
|
+
# query.fetch_first!(id: 2)
|
54
56
|
# RecordNotFound: RecordNotFound
|
55
|
-
def
|
56
|
-
|
57
|
+
def fetch_first!(args={})
|
58
|
+
fetch_first || raise(Cassie::Queries::RecordNotFound.new('CQL row does not exist'))
|
57
59
|
end
|
58
60
|
end
|
59
61
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cassie
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0.beta.
|
4
|
+
version: 1.0.0.beta.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Evan Prothro
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-08-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: cassandra-driver
|
@@ -132,6 +132,7 @@ files:
|
|
132
132
|
- lib/cassie/queries/statement.rb
|
133
133
|
- lib/cassie/queries/statement/assignment.rb
|
134
134
|
- lib/cassie/queries/statement/assignments.rb
|
135
|
+
- lib/cassie/queries/statement/batches.rb
|
135
136
|
- lib/cassie/queries/statement/callbacks.rb
|
136
137
|
- lib/cassie/queries/statement/conditions.rb
|
137
138
|
- lib/cassie/queries/statement/consistency.rb
|