cassie 1.0.0.beta.1 → 1.0.0.beta.3
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/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
|