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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2b8f581ee400104d561a3c3bb12ef2cdb95c0f98
4
- data.tar.gz: 931dc3e000852e1ad9a0773aecbc54c0617badbf
3
+ metadata.gz: df9c9472bd40950a8fb5cae45a95ab79661276f6
4
+ data.tar.gz: 79a9cad9af8d1410c73937f4c8ff52da630f40b5
5
5
  SHA512:
6
- metadata.gz: be2ee76d1ede47feeac24b9295cbc2e137bb2807bc45049ca7ca98feb7939a6975a83865cf4eed881871effd7487fa6ed40864765df37298a9e217a0e0e15631
7
- data.tar.gz: 0ec6179d4534a0ac5dd35e737f4b231c6f4ef1d6026a56cc524ee5e348323797fd9b15d82d0365c5b192a3a651cdde49d2676e3011179aa6e6be711106d99972
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
- What you might expect to see:
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 structured to help you create query classes that are reusable, testable and maintainable, so you can sleep better at night.
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.find(username: "eprothro").username
280
+ UsersByUsernameQuery.new.fetch_first(username: "eprothro").username
215
281
  => "eprothro"
216
282
  ```
217
283
 
218
- Override `build_resource` to construct more useful objects
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 an object is supported.
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
- Allowing you to pass an object to the modification method, and binding values will be retrieved from the object
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
@@ -14,7 +14,7 @@ module Cassie::Queries::Statement
14
14
  end
15
15
 
16
16
  def execute
17
- run_callbacks :failure unless super
17
+ super || run_callbacks(:failure)
18
18
  end
19
19
  end
20
20
  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.find(id: 1)
32
+ # query.fetch_first(id: 1)
31
33
  # => {id: 1, name: 'eprothro'}
32
34
  #
33
- # query.find(id: 2)
35
+ # query.fetch_first(id: 2)
34
36
  # => nil
35
- def find(args={})
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.find!(id: 1)
52
+ # query.fetch_first!(id: 1)
51
53
  # => {id: 1, name: 'eprothro'}
52
54
  #
53
- # query.find!(id: 2)
55
+ # query.fetch_first!(id: 2)
54
56
  # RecordNotFound: RecordNotFound
55
- def find!(args={})
56
- find || raise(Cassie::Queries::RecordNotFound)
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
@@ -18,7 +18,7 @@ module Cassie::Queries::Statement
18
18
  # When class doesn't override
19
19
  # simply return a struct with the row data
20
20
  def build_resource(row)
21
- Struct.new(*row.keys).new(*row.values)
21
+ Struct.new(*row.keys.map(&:to_sym)).new(*row.values)
22
22
  end
23
23
  end
24
24
  end
@@ -13,6 +13,8 @@ require_relative 'statement/inserting'
13
13
 
14
14
  module Cassie::Queries
15
15
  module Statement
16
+ class Invalid < StandardError; end
17
+
16
18
  # https://cassandra.apache.org/doc/cql3/CQL.html#selectStmt
17
19
  extend ::ActiveSupport::Concern
18
20
 
@@ -6,5 +6,6 @@ module Cassie
6
6
 
7
7
  class FakeQuery < Cassie::Query
8
8
  include Cassie::Testing::Fake::SessionMethods
9
+
9
10
  end
10
11
  end
@@ -20,5 +20,13 @@ module Cassie::Testing::Fake
20
20
  def empty?
21
21
  rows.empty?
22
22
  end
23
+
24
+ def last_page?
25
+ true
26
+ end
27
+
28
+ def paging_state
29
+ 'that state tho!'
30
+ end
23
31
  end
24
32
  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.1
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-06-06 00:00:00.000000000 Z
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