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 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