cql-rb 2.0.0.pre2 → 2.0.0.rc0

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: dcb7e0235108a8f81d404fd31054f2d1ac4701d4
4
- data.tar.gz: 928b20dfd3e07da9062c3443b57de7b68e82f1df
3
+ metadata.gz: b5d95e28951fcb350531d0899c6a4b12938677c9
4
+ data.tar.gz: 271b515465def1838737f6d92983958f47541f09
5
5
  SHA512:
6
- metadata.gz: 31788daec8a14dd8c2a3a0588e510ff54cef4b8ad289dfb806a978b49035345b760e5602cd5142930acfd2a56e6f289304efb714b5984f117b07e9196833d580
7
- data.tar.gz: 4518fdb25311bd20433aae27094015d4502c53e9f793670a85fca6ed20da351e8065f08197a5621ecec3d82fe3050e6b368c02db5ffa614ddae82baf623b8c19
6
+ metadata.gz: 89b8043dc895260cb8baee75a43652d7150574141863a876a663fb2427672b36ba970f3837813800edd4f106e5fc56d053d75dca2c26889ab0a25b110846a660
7
+ data.tar.gz: 2d910f7d38e3bb0851c6a43b1146d01f84b6c7b5c5d33682bd587dd2b0f4eb2127e5fc26798a491a4b03730fc8a16fa756bbe19445f2ddfb1b3910b8487dc51b
data/README.md CHANGED
@@ -143,6 +143,8 @@ A prepared statement can be run many times, but the CQL parsing will only be don
143
143
 
144
144
  Statements are prepared on all connections and each call to `#execute` selects a random connection to run the query on.
145
145
 
146
+ You should only create a prepared statement for a query once, and then reuse the prepared statement object. Preparing the same CQL over and over again is bad for performance since each preparation requires a roundtrip to _all_ connected Cassandra nodes.
147
+
146
148
  ## Batching
147
149
 
148
150
  If you're using Cassandra 2.0 or later you can build batch requests, either from regular queries or from prepared statements. Batches can consist of `INSERT`, `UPDATE` and `DELETE` statements.
@@ -210,6 +212,16 @@ batch.execute(consistency: :quorum)
210
212
 
211
213
  As you can see you can specify the options either when creating the batch or when sending it (when using the variant where you call `#execute` yourself). The options given to `#execute` take precedence. You can omit the batch type and specify the options as the only parameter when you want to use the the default batch type.
212
214
 
215
+ If you want to execute the same prepared statement multiple times in a batch there is a special variant of the batching feature available from `PreparedStatement`:
216
+
217
+ ```ruby
218
+ # the same counter_statement as in the example above
219
+ counter_statement.batch do |batch|
220
+ batch.add(3, 'some_counter')
221
+ batch.add(2, 'another_counter')
222
+ end
223
+ ```
224
+
213
225
  Cassandra 1.2 also supported batching, but only as a CQL feature, you had to build the batch as a string, and it didn't really play well with prepared statements.
214
226
 
215
227
  ## Paging
@@ -219,15 +231,11 @@ If you're using Cassandra 2.0 or later you can page your query results by adding
219
231
  ```ruby
220
232
  result_page = client.execute("SELECT * FROM large_table WHERE id = 'partition_with_lots_of_data'", page_size: 100)
221
233
 
222
- loop do
234
+ while result_page
223
235
  result_page.each do |row|
224
236
  p row
225
237
  end
226
- if result_page.last_page?
227
- break
228
- else
229
- result_page = result_page.next_page
230
- end
238
+ result_page = result_page.next_page
231
239
  end
232
240
  ```
233
241
 
data/lib/cql.rb CHANGED
@@ -5,15 +5,21 @@ require 'ione'
5
5
 
6
6
  module Cql
7
7
  CqlError = Class.new(StandardError)
8
+ IoError = Ione::IoError
8
9
 
10
+ # @private
9
11
  Promise = Ione::Promise
12
+
13
+ # @private
10
14
  Future = Ione::Future
15
+
16
+ # @private
11
17
  Io = Ione::Io
12
- IoError = Ione::IoError
13
18
  end
14
19
 
15
20
  require 'cql/uuid'
16
21
  require 'cql/time_uuid'
17
22
  require 'cql/compression'
18
23
  require 'cql/protocol'
24
+ require 'cql/auth'
19
25
  require 'cql/client'
@@ -1,7 +1,7 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Cql
4
- module Client
4
+ module Auth
5
5
  # An auth provider is a factory for {Cql::Client::Authenticator} instances
6
6
  # (or objects matching that interface). Its {#create_authenticator} will be
7
7
  # called once for each connection that requires authentication.
@@ -71,46 +71,7 @@ module Cql
71
71
  # @param token [String] a token sent by the server
72
72
  # @return [nil]
73
73
  end
74
-
75
- # Auth provider used for Cassandra's built in authentication.
76
- #
77
- # There is no need to create instances of this class to pass as `:auth_provider`
78
- # to {Cql::Client.connect}, instead use the `:credentials` option and one
79
- # will be created automatically for you.
80
- class PlainTextAuthProvider
81
- def initialize(username, password)
82
- @username = username
83
- @password = password
84
- end
85
-
86
- def create_authenticator(authentication_class)
87
- if authentication_class == PASSWORD_AUTHENTICATOR_FQCN
88
- PlainTextAuthenticator.new(@username, @password)
89
- end
90
- end
91
-
92
- private
93
-
94
- PASSWORD_AUTHENTICATOR_FQCN = 'org.apache.cassandra.auth.PasswordAuthenticator'.freeze
95
- end
96
-
97
- # Authenticator used for Cassandra's built in authentication,
98
- # see {Cql::Client::PlainTextAuthProvider}
99
- class PlainTextAuthenticator
100
- def initialize(username, password)
101
- @username = username
102
- @password = password
103
- end
104
-
105
- def initial_response
106
- "\x00#{@username}\x00#{@password}"
107
- end
108
-
109
- def challenge_response(token)
110
- end
111
-
112
- def authentication_successful(token)
113
- end
114
- end
115
74
  end
116
- end
75
+ end
76
+
77
+ require 'cql/auth/plain_text_auth'
@@ -0,0 +1,46 @@
1
+ # encoding: utf-8
2
+
3
+ module Cql
4
+ module Auth
5
+ # Auth provider used for Cassandra's built in authentication.
6
+ #
7
+ # There is no need to create instances of this class to pass as `:auth_provider`
8
+ # to {Cql::Client.connect}, instead use the `:credentials` option and one
9
+ # will be created automatically for you.
10
+ class PlainTextAuthProvider
11
+ def initialize(username, password)
12
+ @username = username
13
+ @password = password
14
+ end
15
+
16
+ def create_authenticator(authentication_class)
17
+ if authentication_class == PASSWORD_AUTHENTICATOR_FQCN
18
+ PlainTextAuthenticator.new(@username, @password)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ PASSWORD_AUTHENTICATOR_FQCN = 'org.apache.cassandra.auth.PasswordAuthenticator'.freeze
25
+ end
26
+
27
+ # Authenticator used for Cassandra's built in authentication,
28
+ # see {Cql::Auth::PlainTextAuthProvider}
29
+ class PlainTextAuthenticator
30
+ def initialize(username, password)
31
+ @username = username
32
+ @password = password
33
+ end
34
+
35
+ def initial_response
36
+ "\x00#{@username}\x00#{@password}"
37
+ end
38
+
39
+ def challenge_response(token)
40
+ end
41
+
42
+ def authentication_successful(token)
43
+ end
44
+ end
45
+ end
46
+ end
data/lib/cql/client.rb CHANGED
@@ -20,7 +20,6 @@ module Cql
20
20
  TimeoutError = Class.new(CqlError)
21
21
  ClientError = Class.new(CqlError)
22
22
  AuthenticationError = Class.new(ClientError)
23
- IncompleteTraceError = Class.new(ClientError)
24
23
  UnsupportedProtocolVersionError = Class.new(ClientError)
25
24
  NotPreparedError = Class.new(ClientError)
26
25
 
@@ -92,9 +91,6 @@ module Cql
92
91
  # @param [Hash] options
93
92
  # @option options [Array<String>] :hosts (['localhost']) One or more
94
93
  # hostnames used as seed nodes when connecting. Duplicates will be removed.
95
- # @option options [String] :host ('localhost') A comma separated list of
96
- # hostnames to use as seed nodes. This is a backwards-compatible version
97
- # of the :hosts option, and is deprecated.
98
94
  # @option options [String] :port (9042) The port to connect to, this port
99
95
  # will be used for all nodes. Because the `system.peers` table does not
100
96
  # contain the port that the nodes are listening on, the port must be the
@@ -143,7 +139,6 @@ require 'cql/client/connector'
143
139
  require 'cql/client/null_logger'
144
140
  require 'cql/client/column_metadata'
145
141
  require 'cql/client/result_metadata'
146
- require 'cql/client/query_trace'
147
142
  require 'cql/client/execute_options_decoder'
148
143
  require 'cql/client/keyspace_changer'
149
144
  require 'cql/client/client'
@@ -152,5 +147,4 @@ require 'cql/client/batch'
152
147
  require 'cql/client/query_result'
153
148
  require 'cql/client/void_result'
154
149
  require 'cql/client/request_runner'
155
- require 'cql/client/authenticators'
156
150
  require 'cql/client/peer_discovery'
@@ -2,6 +2,13 @@
2
2
 
3
3
  module Cql
4
4
  module Client
5
+ # Batches let you send multiple queries (`INSERT`, `UPDATE` and `DELETE`) in
6
+ # one go. This can lead to better performance, and depending on the options
7
+ # you specify can also give you different consistency guarantees.
8
+ #
9
+ # Batches can contain a mix of different queries and prepared statements.
10
+ #
11
+ # @see Cql::Client::Client#batch
5
12
  class Batch
6
13
  # @!method add(cql_or_prepared_statement, *bound_values)
7
14
  #
@@ -37,6 +44,10 @@ module Cql
37
44
  # @return [Cql::Client::VoidResult] a batch always returns a void result
38
45
  end
39
46
 
47
+ # A convenient wrapper that makes it easy to build batches of multiple
48
+ # executions of the same prepared statement.
49
+ #
50
+ # @see Cql::Client::PreparedStatement#batch
40
51
  class PreparedStatementBatch
41
52
  # @!method add(*bound_values)
42
53
  #
@@ -2,18 +2,29 @@
2
2
 
3
3
  module Cql
4
4
  module Client
5
+ # A CQL client manages connections to one or more Cassandra nodes and you use
6
+ # it run queries, insert and update data, prepare statements and switch
7
+ # keyspaces.
8
+ #
9
+ # To get a reference to a client you call {Cql::Client.connect}. When you
10
+ # don't need the client anymore you can call {#close} to close all connections.
11
+ #
12
+ # Internally the client runs an IO reactor in a background thread. The reactor
13
+ # handles all IO and manages the connections to the Cassandra nodes. This
14
+ # makes it possible for the client to handle highly concurrent applications
15
+ # very efficiently.
16
+ #
17
+ # Client instances are threadsafe and you only need a single instance for in
18
+ # an application. Using multiple instances is more likely to lead to worse
19
+ # performance than better.
20
+ #
21
+ # Because the client opens sockets, and runs threads it cannot be used by
22
+ # the child created when forking a process. If your application forks (for
23
+ # example applications running in the Unicorn application server or Resque
24
+ # task queue) you _must_ connect after forking.
25
+ #
26
+ # @see Cql::Client.connect
5
27
  class Client
6
- # @!method connect
7
- #
8
- # Connect to all nodes. See {Cql::Client.connect} for the full
9
- # documentation.
10
- #
11
- # This method needs to be called before any other. Calling it again will
12
- # have no effect.
13
- #
14
- # @see Cql::Client.connect
15
- # @return [Cql::Client]
16
-
17
28
  # @!method close
18
29
  #
19
30
  # Disconnect from all nodes.
@@ -43,7 +54,7 @@ module Cql
43
54
  # @raise [Cql::NotConnectedError] raised when the client is not connected
44
55
  # @return [nil]
45
56
 
46
- # @!method execute(cql, *values, options_or_consistency={})
57
+ # @!method execute(cql, *values, options={})
47
58
  #
48
59
  # Execute a CQL statement, optionally passing bound values.
49
60
  #
@@ -55,7 +66,8 @@ module Cql
55
66
  # bound values when you will issue the request multiple times, prepared
56
67
  # statements are almost always a better choice.
57
68
  #
58
- # @note On-the-fly bound values are not supported in Cassandra 1.2
69
+ # _Please note that on-the-fly bound values are only supported by Cassandra
70
+ # 2.0 and above._
59
71
  #
60
72
  # @example A simple CQL query
61
73
  # result = client.execute("SELECT * FROM users WHERE user_name = 'sue'")
@@ -75,6 +87,15 @@ module Cql
75
87
  # @example Specifying the consistency and other options
76
88
  # client.execute("SELECT * FROM users", consistency: :all, timeout: 1.5)
77
89
  #
90
+ # @example Loading a big result page by page
91
+ # result_page = client.execute("SELECT * FROM large_table WHERE id = 'partition_with_lots_of_data'", page_size: 100)
92
+ # while result_page
93
+ # result_page.each do |row|
94
+ # p row
95
+ # end
96
+ # result_page = result_page.next_page
97
+ # end
98
+ #
78
99
  # @example Activating tracing for a query
79
100
  # result = client.execute("SELECT * FROM users", tracing: true)
80
101
  # p result.trace_id
@@ -88,24 +109,26 @@ module Cql
88
109
  # integers, and DOUBLE instead of FLOAT for floating point numbers. It
89
110
  # is not recommended to use this feature for anything but convenience,
90
111
  # and the algorithm used to guess types is to be considered experimental.
91
- # @param [Hash] options_or_consistency Either a consistency as a symbol
92
- # (e.g. `:quorum`), or a options hash (see below). Passing a symbol is
93
- # equivalent to passing the options `consistency: <symbol>`.
94
- # @option options_or_consistency [Symbol] :consistency (:quorum) The
112
+ # @param [Hash] options
113
+ # @option options [Symbol] :consistency (:quorum) The
95
114
  # consistency to use for this query.
96
- # @option options_or_consistency [Symbol] :serial_consistency (nil) The
115
+ # @option options [Symbol] :serial_consistency (nil) The
97
116
  # consistency to use for conditional updates (`:serial` or
98
117
  # `:local_serial`), see the CQL documentation for the semantics of
99
118
  # serial consistencies and conditional updates. The default is assumed
100
119
  # to be `:serial` by the server if none is specified. Ignored for non-
101
120
  # conditional queries.
102
- # @option options_or_consistency [Integer] :timeout (nil) How long to wait
121
+ # @option options [Integer] :timeout (nil) How long to wait
103
122
  # for a response. If this timeout expires a {Cql::TimeoutError} will
104
123
  # be raised.
105
- # @option options_or_consistency [Boolean] :trace (false) Request tracing
124
+ # @option options [Boolean] :trace (false) Request tracing
106
125
  # for this request. See {Cql::Client::QueryResult} and
107
126
  # {Cql::Client::VoidResult} for how to retrieve the tracing data.
108
- # @option options_or_consistency [Array] :type_hints (nil) When passing
127
+ # @option options [Integer] :page_size (nil) Instead of
128
+ # returning all rows, return the response in pages of this size. The
129
+ # first result will contain the first page, to load subsequent pages
130
+ # use {Cql::Client::QueryResult#next_page}.
131
+ # @option options [Array] :type_hints (nil) When passing
109
132
  # on-the-fly bound values the request encoder will have to guess what
110
133
  # types to encode the values as. Using this option you can give it hints
111
134
  # and avoid it guessing wrong. The hints must be an array that has the
@@ -127,7 +150,7 @@ module Cql
127
150
  # @!method prepare(cql)
128
151
  #
129
152
  # Returns a prepared statement that can be run over and over again with
130
- # different values.
153
+ # different bound values.
131
154
  #
132
155
  # @see Cql::Client::PreparedStatement
133
156
  # @param [String] cql The CQL to prepare
@@ -203,7 +226,7 @@ module Cql
203
226
  @port = options[:port] || DEFAULT_PORT
204
227
  @connection_timeout = options[:connection_timeout] || DEFAULT_CONNECTION_TIMEOUT
205
228
  @credentials = options[:credentials]
206
- @auth_provider = options[:auth_provider] || @credentials && PlainTextAuthProvider.new(*@credentials.values_at(:username, :password))
229
+ @auth_provider = options[:auth_provider] || @credentials && Auth::PlainTextAuthProvider.new(*@credentials.values_at(:username, :password))
207
230
  @connected = false
208
231
  @connecting = false
209
232
  @closing = false
@@ -314,7 +337,7 @@ module Cql
314
337
  MAX_RECONNECTION_ATTEMPTS = 5
315
338
 
316
339
  def extract_hosts(options)
317
- if options[:hosts]
340
+ if options[:hosts] && options[:hosts].any?
318
341
  options[:hosts].uniq
319
342
  elsif options[:host]
320
343
  options[:host].split(',').uniq
@@ -7,7 +7,7 @@ module Cql
7
7
  # the type as a symbol (e.g. `:varchar`, `:int`, `:date`).
8
8
  class ColumnMetadata
9
9
  attr_reader :keyspace, :table, :column_name, :type
10
-
10
+
11
11
  # @private
12
12
  def initialize(*args)
13
13
  @keyspace, @table, @column_name, @type = args
@@ -5,7 +5,7 @@ module Cql
5
5
  # @private
6
6
  class KeyspaceChanger
7
7
  KEYSPACE_NAME_PATTERN = /^\w[\w\d_]*$|^"\w[\w\d_]*"$/
8
-
8
+
9
9
  def initialize(request_runner=RequestRunner.new)
10
10
  @request_runner = request_runner
11
11
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cql
4
4
  module Client
5
+ # @private
5
6
  class PeerDiscovery
6
7
  def initialize(seed_connections)
7
8
  @seed_connections = seed_connections
@@ -2,6 +2,34 @@
2
2
 
3
3
  module Cql
4
4
  module Client
5
+ # A prepared statement are CQL queries that have been sent to the server
6
+ # to be precompiled, so that when executed only their ID and not the whole
7
+ # CQL string need to be sent. They support bound values, or placeholders
8
+ # for values.
9
+ #
10
+ # Using a prepared statement for any query that you execute more than once
11
+ # is highly recommended. Besides the benefit of having less network overhead,
12
+ # and less processing overhead on the server side, they don't require you
13
+ # to build CQL strings and escape special characters, or format non-character
14
+ # data such as UUIDs, different numeric types, or collections, in the
15
+ # correct way.
16
+ #
17
+ # You should only prepare a statement once and reuse the prepared statement
18
+ # object every time you want to execute that particular query. The statement
19
+ # object will make sure that it is prepared on all connections, and will
20
+ # (lazily, but transparently) make sure it is prepared on any new connections.
21
+ #
22
+ # It is an anti-pattern to prepare the same query over and over again. It is
23
+ # bad for performance, since every preparation requires a roundtrip to all
24
+ # connected servers, and because of some bookeeping that is done to support
25
+ # automatic preparation on new connections, it will lead to unnecessary
26
+ # extra memory usage. There is no performance benefit in creating multiple
27
+ # prepared statement objects for the same query.
28
+ #
29
+ # Prepared statement objects are completely thread safe and can be shared
30
+ # across all threads in your application.
31
+ #
32
+ # @see Cql::Client::Client#prepare
5
33
  class PreparedStatement
6
34
  # Metadata describing the bound values
7
35
  #
@@ -28,10 +56,22 @@ module Cql
28
56
  # arguments should result in an `ArgumentError` or `TypeError` being
29
57
  # raised.
30
58
  #
31
- # @param args [Array] the values for the bound parameters. The last
32
- # argument can also be an options hash or a symbol (as a shortcut for
33
- # specifying the consistency), see {Cql::Client::Client#execute} for
34
- # full details.
59
+ # @example Preparing and executing an `INSERT` statement
60
+ # statement = client.prepare(%(INSERT INTO metrics (id, time, value) VALUES (?, NOW(), ?)))
61
+ # statement.execute(1234, 23432)
62
+ # statement.execute(2345, 34543, tracing: true)
63
+ # statement.execute(3456, 45654, consistency: :one)
64
+ #
65
+ # @example Preparing and executing a `SELECT` statement
66
+ # statement = client.prepare(%(SELECT * FROM metrics WHERE id = ? AND time > ?))
67
+ # result = statement.execute(1234, Time.now - 3600)
68
+ # result.each do |row|
69
+ # p row
70
+ # end
71
+ #
72
+ # @param args [Array] the values for the bound parameters, and an optional
73
+ # hash of options as last argument – see {Cql::Client::Client#execute}
74
+ # for details on which options are available.
35
75
  # @raise [ArgumentError] raised when number of argument does not match
36
76
  # the number of parameters needed to be bound to the statement.
37
77
  # @raise [Cql::NotConnectedError] raised when the client is not connected
@@ -45,6 +85,45 @@ module Cql
45
85
  # (see {Cql::Client::VoidResult}).
46
86
  def execute(*args)
47
87
  end
88
+
89
+ # Yields a batch when called with a block. The batch is automatically
90
+ # executed at the end of the block and the result is returned.
91
+ #
92
+ # Returns a batch when called wihtout a block. The batch will remember
93
+ # the options given and merge these with any additional options given
94
+ # when {Cql::Client::PreparedStatementBatch#execute} is called.
95
+ #
96
+ # The batch yielded or returned by this method is not identical to the
97
+ # regular batch objects yielded or returned by {Cql::Client::Client#batch}.
98
+ # These prepared statement batch objects can be used only to add multiple
99
+ # executions of the same prepared statement.
100
+ #
101
+ # Please note that the batch object returned by this method _is not thread
102
+ # safe_.
103
+ #
104
+ # The type parameter can be ommitted and the options can then be given
105
+ # as first parameter.
106
+ #
107
+ # @example Executing a prepared statement in a batch
108
+ # statement = client.prepare(%(INSERT INTO metrics (id, time, value) VALUES (?, NOW(), ?)))
109
+ # statement.batch do |batch|
110
+ # batch.add(1234, 23423)
111
+ # batch.add(2346, 13)
112
+ # batch.add(2342, 2367)
113
+ # batch.add(4562, 1231)
114
+ # end
115
+ #
116
+ # @see Cql::Client::PreparedStatementBatch
117
+ # @see Cql::Client::Client#batch
118
+ # @param [Symbol] type the type of batch, must be one of `:logged`,
119
+ # `:unlogged` and `:counter`. The precise meaning of these is defined
120
+ # in the CQL specification.
121
+ # @yieldparam [Cql::Client::PreparedStatementBatch] batch the batch
122
+ # @return [Cql::Client::VoidResult, Cql::Client::Batch] when no block is
123
+ # given the batch is returned, when a block is given the result of
124
+ # executing the batch is returned (see {Cql::Client::Batch#execute}).
125
+ def batch(type=:logged, options={})
126
+ end
48
127
  end
49
128
 
50
129
  # @private
@@ -2,6 +2,16 @@
2
2
 
3
3
  module Cql
4
4
  module Client
5
+ # Query results encapsulate the rows returned by a query.
6
+ #
7
+ # In addition to containing the rows it contains metadata about the data
8
+ # types of the columns of the rows, and it knows the ID of the trace,
9
+ # if tracing was requested for the query.
10
+ #
11
+ # When paging over a big result you can use {#last_page?} to find out if the
12
+ # page is the last, or {#next_page} to retrieve the next page.
13
+ #
14
+ # `QueryResult` is an `Enumerable` so it can be mapped, filtered, reduced, etc.
5
15
  class QueryResult
6
16
  include Enumerable
7
17
 
@@ -37,8 +47,31 @@ module Cql
37
47
  @rows.each(&block)
38
48
  end
39
49
  alias_method :each_row, :each
50
+
51
+ # Returns true when there are no more pages to load.
52
+ #
53
+ # This is only relevant when you have requested paging of the results with
54
+ # the `:page_size` option to {Cql::Client::Client#execute} or
55
+ # {Cql::Client::PreparedStatement#execute}.
56
+ #
57
+ # @see Cql::Client::Client#execute
58
+ def last_page?
59
+ true
60
+ end
61
+
62
+ # Returns the next page or nil when there is no next page.
63
+ #
64
+ # This is only relevant when you have requested paging of the results with
65
+ # the `:page_size` option to {Cql::Client::Client#execute} or
66
+ # {Cql::Client::PreparedStatement#execute}.
67
+ #
68
+ # @see Cql::Client::Client#execute
69
+ def next_page
70
+ nil
71
+ end
40
72
  end
41
73
 
74
+ # @private
42
75
  class PagedQueryResult < QueryResult
43
76
  def metadata
44
77
  @result.metadata
@@ -88,6 +121,7 @@ module Cql
88
121
  end
89
122
 
90
123
  def next_page
124
+ return Future.resolved(nil) if last_page?
91
125
  @client.execute(@request.cql, *@request.values, @options)
92
126
  end
93
127
  end
@@ -100,6 +134,7 @@ module Cql
100
134
  end
101
135
 
102
136
  def next_page
137
+ return Future.resolved(nil) if last_page?
103
138
  @prepared_statement.execute(*@request.values, @options)
104
139
  end
105
140
  end
@@ -121,7 +156,10 @@ module Cql
121
156
  end
122
157
 
123
158
  def next_page
124
- synchronous_backtrace { self.class.new(@result.next_page.value) }
159
+ synchronous_backtrace do
160
+ asynchronous_result = @result.next_page.value
161
+ asynchronous_result && self.class.new(asynchronous_result)
162
+ end
125
163
  end
126
164
  end
127
165
 
@@ -2,6 +2,9 @@
2
2
 
3
3
  module Cql
4
4
  module Client
5
+ # A collection of metadata (keyspace, table, name and type) of a result set.
6
+ #
7
+ # @see Cql::Client::ColumnMetadata
5
8
  class ResultMetadata
6
9
  include Enumerable
7
10
 
@@ -13,7 +16,6 @@ module Cql
13
16
  # Returns the column metadata
14
17
  #
15
18
  # @return [ColumnMetadata] column_metadata the metadata for the column
16
- #
17
19
  def [](column_name)
18
20
  @metadata[column_name]
19
21
  end
@@ -22,7 +24,6 @@ module Cql
22
24
  #
23
25
  # @yieldparam [ColumnMetadata] metadata the metadata for each column
24
26
  # @return [Enumerable<ColumnMetadata>]
25
- #
26
27
  def each(&block)
27
28
  @metadata.each_value(&block)
28
29
  end
@@ -2,6 +2,16 @@
2
2
 
3
3
  module Cql
4
4
  module Client
5
+ # Many CQL queries do not return any rows, but they can still return
6
+ # data about the query, for example the trace ID. This class exist to make
7
+ # that data available.
8
+ #
9
+ # It has the exact same API as {Cql::Client::QueryResult} so that you don't
10
+ # need to check the return value of for example {Cql::Client::Client#execute}.
11
+ #
12
+ # @see Cql::Client::QueryResult
13
+ # @see Cql::Client::Client#execute
14
+ # @see Cql::Client::PreparedStatement#execute
5
15
  class VoidResult
6
16
  include Enumerable
7
17
 
@@ -26,6 +36,16 @@ module Cql
26
36
  true
27
37
  end
28
38
 
39
+ # Always returns true
40
+ def last_page?
41
+ true
42
+ end
43
+
44
+ # Always returns nil
45
+ def next_page
46
+ nil
47
+ end
48
+
29
49
  # No-op for API compatibility with {QueryResult}.
30
50
  #
31
51
  # @return [Enumerable]
data/lib/cql/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Cql
4
- VERSION = '2.0.0.pre2'.freeze
4
+ VERSION = '2.0.0.rc0'.freeze
5
5
  end
@@ -4,7 +4,7 @@ require 'spec_helper'
4
4
 
5
5
 
6
6
  module Cql
7
- module Client
7
+ module Auth
8
8
  describe PlainTextAuthProvider do
9
9
  let :auth_provider do
10
10
  described_class.new('foo', 'bar')
@@ -11,7 +11,7 @@ module Cql
11
11
  end
12
12
 
13
13
  let :default_connection_options do
14
- {:host => 'example.com', :port => 12321, :io_reactor => io_reactor, :logger => logger}
14
+ {:hosts => %w[example.com], :port => 12321, :io_reactor => io_reactor, :logger => logger}
15
15
  end
16
16
 
17
17
  let :connection_options do
@@ -162,6 +162,19 @@ module Cql
162
162
  expect { client.connect.value }.to raise_error('bork')
163
163
  end
164
164
 
165
+ it 'connects to localhost by default' do
166
+ connection_options.delete(:hosts)
167
+ c = described_class.new(connection_options)
168
+ c.connect.value
169
+ connections.map(&:host).should == %w[localhost]
170
+ end
171
+
172
+ it 'connects to localhost when an empty list of hosts is given' do
173
+ c = described_class.new(connection_options.merge(hosts: []))
174
+ c.connect.value
175
+ connections.map(&:host).should == %w[localhost]
176
+ end
177
+
165
178
  context 'when connecting to multiple hosts' do
166
179
  before do
167
180
  client.close.value
@@ -175,6 +188,7 @@ module Cql
175
188
  end
176
189
 
177
190
  it 'connects to all hosts, when given as a comma-sepatated string' do
191
+ connection_options.delete(:hosts)
178
192
  c = described_class.new(connection_options.merge(host: 'h1.example.com,h2.example.com,h3.example.com'))
179
193
  c.connect.value
180
194
  connections.should have(3).items
@@ -510,7 +524,7 @@ module Cql
510
524
 
511
525
  context 'when the server requests authentication' do
512
526
  let :auth_provider do
513
- PlainTextAuthProvider.new('foo', 'bar')
527
+ Auth::PlainTextAuthProvider.new('foo', 'bar')
514
528
  end
515
529
 
516
530
  def accepting_request_handler(request, *)
@@ -236,7 +236,6 @@ module Cql
236
236
 
237
237
  it 'returns a result which knows when there are no more pages' do
238
238
  result = statement.execute(11, 'foo', page_size: 2).value
239
- result = result.next_page.value
240
239
  result.should be_last_page
241
240
  end
242
241
  end
@@ -84,6 +84,18 @@ module Cql
84
84
  result.should_not be_empty
85
85
  end
86
86
  end
87
+
88
+ describe '#last_page?' do
89
+ it 'returns true' do
90
+ result.should be_last_page
91
+ end
92
+ end
93
+
94
+ describe '#next_page' do
95
+ it 'returns nil' do
96
+ result.next_page.should be_nil
97
+ end
98
+ end
87
99
  end
88
100
 
89
101
  describe LazyQueryResult do
@@ -209,11 +221,16 @@ module Cql
209
221
 
210
222
  describe '#next_page' do
211
223
  let :next_query_result do
212
- double(:next_query_result, paging_state: 'thenextpagingstate')
224
+ described_class.new(client, request, double(:next_query_result, paging_state: 'thenextpagingstate'), options)
225
+ end
226
+
227
+ let :last_query_result do
228
+ described_class.new(client, request, double(:next_query_result, paging_state: nil), options)
213
229
  end
214
230
 
215
231
  before do
216
232
  client.stub(:execute).and_return(Future.resolved(next_query_result))
233
+ client.stub(:execute).with(anything, anything, hash_including(paging_state: 'thenextpagingstate')).and_return(Future.resolved(last_query_result))
217
234
  end
218
235
 
219
236
  it 'calls the client and passes the paging state' do
@@ -252,6 +269,11 @@ module Cql
252
269
  f = paged_query_result.next_page
253
270
  f.value.should equal(next_query_result)
254
271
  end
272
+
273
+ it 'returns nil when it is the last page' do
274
+ f = paged_query_result.next_page.value.next_page.value.next_page
275
+ f.value.should be_nil
276
+ end
255
277
  end
256
278
  end
257
279
 
@@ -280,16 +302,20 @@ module Cql
280
302
 
281
303
  describe '#next_page' do
282
304
  let :next_query_result do
283
- double(:next_query_result, paging_state: 'thenextpagingstate')
305
+ described_class.new(statement, request, double(:next_query_result, paging_state: 'thenextpagingstate'), options)
306
+ end
307
+
308
+ let :last_query_result do
309
+ described_class.new(statement, request, double(:next_query_result, paging_state: nil), options)
284
310
  end
285
311
 
286
312
  before do
287
313
  statement.stub(:execute).and_return(Future.resolved(next_query_result))
314
+ statement.stub(:execute).with(anything, anything, hash_including(paging_state: 'thenextpagingstate')).and_return(Future.resolved(last_query_result))
288
315
  end
289
316
 
290
317
  it 'calls the statement and passes the paging state' do
291
318
  paged_query_result.next_page.value
292
- statement.should have_received(:execute).with(anything, anything, hash_including(paging_state: 'thepagingstate'))
293
319
  end
294
320
 
295
321
  it 'calls the statement and passes the options' do
@@ -312,6 +338,11 @@ module Cql
312
338
  f = paged_query_result.next_page
313
339
  f.value.should equal(next_query_result)
314
340
  end
341
+
342
+ it 'returns nil when it is the last page' do
343
+ f = paged_query_result.next_page.value.next_page.value.next_page
344
+ f.value.should be_nil
345
+ end
315
346
  end
316
347
  end
317
348
 
@@ -336,6 +367,11 @@ module Cql
336
367
  second_page.next_page
337
368
  next_query_result.should have_received(:next_page)
338
369
  end
370
+
371
+ it 'returns nil when it is the last page' do
372
+ asynchronous_paged_query_result.stub(:next_page).and_return(Future.resolved(nil))
373
+ paged_query_result.next_page.should be_nil
374
+ end
339
375
  end
340
376
 
341
377
  describe '#last_page?' do
@@ -12,6 +12,18 @@ module Cql
12
12
  end
13
13
  end
14
14
 
15
+ describe '#last_page?' do
16
+ it 'is true' do
17
+ described_class.new.should be_last_page
18
+ end
19
+ end
20
+
21
+ describe '#next_page' do
22
+ it 'returns nil' do
23
+ described_class.new.next_page.should be_nil
24
+ end
25
+ end
26
+
15
27
  describe '#trace_id' do
16
28
  it 'is nil' do
17
29
  described_class.new.trace_id.should be_nil
@@ -192,7 +192,7 @@ describe 'A CQL client' do
192
192
 
193
193
  it 'raises an error when only an auth provider has been given' do
194
194
  pending('authentication not configured', unless: authentication_enabled) do
195
- auth_provider = Cql::Client::PlainTextAuthProvider.new('cassandra', 'cassandra')
195
+ auth_provider = Cql::Auth::PlainTextAuthProvider.new('cassandra', 'cassandra')
196
196
  expect { Cql::Client.connect(connection_options.merge(credentials: nil, auth_provider: auth_provider, protocol_version: 1)) }.to raise_error(Cql::AuthenticationError)
197
197
  end
198
198
  end
@@ -209,7 +209,7 @@ describe 'A CQL client' do
209
209
  end
210
210
 
211
211
  it 'uses the auth provider given in the :auth_provider option' do
212
- auth_provider = Cql::Client::PlainTextAuthProvider.new('cassandra', 'cassandra')
212
+ auth_provider = Cql::Auth::PlainTextAuthProvider.new('cassandra', 'cassandra')
213
213
  client = Cql::Client.connect(connection_options.merge(auth_provider: auth_provider, credentials: nil))
214
214
  client.execute('SELECT * FROM system.schema_keyspaces')
215
215
  end
@@ -228,7 +228,7 @@ describe 'A CQL client' do
228
228
  it 'raises an error when the credentials are bad' do
229
229
  pending('authentication not configured', unless: authentication_enabled) do
230
230
  expect {
231
- auth_provider = Cql::Client::PlainTextAuthProvider.new('foo', 'bar')
231
+ auth_provider = Cql::Auth::PlainTextAuthProvider.new('foo', 'bar')
232
232
  Cql::Client.connect(connection_options.merge(auth_provider: auth_provider, credentials: nil))
233
233
  }.to raise_error(Cql::AuthenticationError)
234
234
  end
@@ -408,6 +408,18 @@ describe 'A CQL client' do
408
408
  result_page.count.should == row_count - page_size
409
409
  result_page.should be_last_page
410
410
  end
411
+
412
+ it 'returns nil from #next_page when the last page has been returned' do
413
+ page_size = row_count/5 + 1
414
+ statement = client.prepare('SELECT * FROM counters')
415
+ result_page = statement.execute(page_size: page_size)
416
+ page_count = 0
417
+ while result_page
418
+ page_count += 1
419
+ result_page = result_page.next_page
420
+ end
421
+ page_count.should == 5
422
+ end
411
423
  end
412
424
 
413
425
  context 'with error conditions' do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cql-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.pre2
4
+ version: 2.0.0.rc0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Theo Hultberg
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-05-06 00:00:00.000000000 Z
11
+ date: 2014-05-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ione
@@ -34,8 +34,9 @@ files:
34
34
  - .yardopts
35
35
  - README.md
36
36
  - lib/cql.rb
37
+ - lib/cql/auth.rb
38
+ - lib/cql/auth/plain_text_auth.rb
37
39
  - lib/cql/client.rb
38
- - lib/cql/client/authenticators.rb
39
40
  - lib/cql/client/batch.rb
40
41
  - lib/cql/client/client.rb
41
42
  - lib/cql/client/column_metadata.rb
@@ -47,7 +48,6 @@ files:
47
48
  - lib/cql/client/peer_discovery.rb
48
49
  - lib/cql/client/prepared_statement.rb
49
50
  - lib/cql/client/query_result.rb
50
- - lib/cql/client/query_trace.rb
51
51
  - lib/cql/client/request_runner.rb
52
52
  - lib/cql/client/result_metadata.rb
53
53
  - lib/cql/client/void_result.rb
@@ -91,7 +91,7 @@ files:
91
91
  - lib/cql/time_uuid.rb
92
92
  - lib/cql/uuid.rb
93
93
  - lib/cql/version.rb
94
- - spec/cql/client/authenticators_spec.rb
94
+ - spec/cql/auth/plain_text_auth_spec.rb
95
95
  - spec/cql/client/batch_spec.rb
96
96
  - spec/cql/client/client_spec.rb
97
97
  - spec/cql/client/column_metadata_spec.rb
@@ -102,7 +102,6 @@ files:
102
102
  - spec/cql/client/peer_discovery_spec.rb
103
103
  - spec/cql/client/prepared_statement_spec.rb
104
104
  - spec/cql/client/query_result_spec.rb
105
- - spec/cql/client/query_trace_spec.rb
106
105
  - spec/cql/client/request_runner_spec.rb
107
106
  - spec/cql/client/void_result_spec.rb
108
107
  - spec/cql/compression/compression_common.rb
@@ -174,7 +173,7 @@ signing_key:
174
173
  specification_version: 4
175
174
  summary: Cassandra CQL3 driver
176
175
  test_files:
177
- - spec/cql/client/authenticators_spec.rb
176
+ - spec/cql/auth/plain_text_auth_spec.rb
178
177
  - spec/cql/client/batch_spec.rb
179
178
  - spec/cql/client/client_spec.rb
180
179
  - spec/cql/client/column_metadata_spec.rb
@@ -185,7 +184,6 @@ test_files:
185
184
  - spec/cql/client/peer_discovery_spec.rb
186
185
  - spec/cql/client/prepared_statement_spec.rb
187
186
  - spec/cql/client/query_result_spec.rb
188
- - spec/cql/client/query_trace_spec.rb
189
187
  - spec/cql/client/request_runner_spec.rb
190
188
  - spec/cql/client/void_result_spec.rb
191
189
  - spec/cql/compression/compression_common.rb
@@ -1,46 +0,0 @@
1
- # encoding: utf-8
2
-
3
- module Cql
4
- module Client
5
- # @private
6
- class QueryTrace
7
- attr_reader :coordinator, :cql, :started_at, :duration, :events
8
-
9
- # @private
10
- def initialize(session, events)
11
- if session
12
- raise IncompleteTraceError, 'Trace incomplete, try loading it again' unless session['duration']
13
- @coordinator = session['coordinator']
14
- @cql = (parameters = session['parameters']) && parameters['query']
15
- @started_at = session['started_at']
16
- @duration = session['duration']/1_000_000.0
17
- if events
18
- @events = events.map { |e| TraceEvent.new(e) }.freeze
19
- end
20
- else
21
- @events = [].freeze
22
- end
23
- end
24
- end
25
-
26
- # @private
27
- class TraceEvent
28
- attr_reader :activity, :source, :source_elapsed, :time
29
-
30
- # @private
31
- def initialize(event)
32
- @activity = event['activity']
33
- @source = event['source']
34
- @source_elapsed = event['source_elapsed']/1_000_000.0
35
- @time = event['event_id'].to_time
36
- end
37
- end
38
-
39
- # @private
40
- class NullQueryTrace < QueryTrace
41
- def initialize
42
- super(nil, nil)
43
- end
44
- end
45
- end
46
- end
@@ -1,138 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require 'spec_helper'
4
-
5
-
6
- module Cql
7
- module Client
8
- describe QueryTrace do
9
- let :trace do
10
- described_class.new(session_row, event_rows)
11
- end
12
-
13
- let :started_at do
14
- Time.now
15
- end
16
-
17
- let :session_row do
18
- {
19
- 'session_id' => Uuid.new('a1028490-3f05-11e3-9531-fb72eff05fbb'),
20
- 'coordinator' => IPAddr.new('127.0.0.1'),
21
- 'duration' => 1263,
22
- 'parameters' => {
23
- 'query' => 'SELECT * FROM something'
24
- },
25
- 'request' => 'Execute CQL3 query',
26
- 'started_at' => started_at
27
- }
28
- end
29
-
30
- let :event_rows do
31
- [
32
- {
33
- 'session_id' => Uuid.new('a1028490-3f05-11e3-9531-fb72eff05fbb'),
34
- 'event_id' => TimeUuid.new('a1028491-3f05-11e3-9531-fb72eff05fbb'),
35
- 'activity' => 'Parsing statement',
36
- 'source' => IPAddr.new('127.0.0.1'),
37
- 'source_elapsed' => 52,
38
- 'thread' => 'Native-Transport-Requests:126'
39
- },
40
- {
41
- 'session_id' => Uuid.new('a1028490-3f05-11e3-9531-fb72eff05fbb'),
42
- 'event_id' => TimeUuid.new('a1028492-3f05-11e3-9531-fb72eff05fbb'),
43
- 'activity' => 'Peparing statement',
44
- 'source' => IPAddr.new('127.0.0.1'),
45
- 'source_elapsed' => 54,
46
- 'thread' => 'Native-Transport-Requests:126'
47
- },
48
- ]
49
- end
50
-
51
- context 'when the session is nil' do
52
- it 'returns nil from all methods' do
53
- trace = described_class.new(nil, nil)
54
- trace.coordinator.should be_nil
55
- trace.cql.should be_nil
56
- trace.started_at.should be_nil
57
- trace.events.should be_empty
58
- end
59
- end
60
-
61
- context 'when the duration field of the session is nil' do
62
- it 'raises an IncompleteTraceError' do
63
- session_row['duration'] = nil
64
- expect { described_class.new(session_row, event_rows) }.to raise_error(IncompleteTraceError)
65
- end
66
- end
67
-
68
- describe '#coordinator' do
69
- it 'returns the IP address of the coordinator node' do
70
- trace.coordinator.should == IPAddr.new('127.0.0.1')
71
- end
72
- end
73
-
74
- describe '#cql' do
75
- it 'returns the query' do
76
- trace.cql.should == 'SELECT * FROM something'
77
- end
78
- end
79
-
80
- describe '#started_at' do
81
- it 'returns the time the request started' do
82
- trace.started_at.should eql(started_at)
83
- end
84
- end
85
-
86
- describe '#duration' do
87
- it 'returns the duration in seconds' do
88
- trace.duration.should == 0.001263
89
- end
90
- end
91
-
92
- describe '#events' do
93
- it 'returns a list of TraceEvents' do
94
- trace.events.should have(2).items
95
- trace.events.first.should be_a(TraceEvent)
96
- end
97
-
98
- it 'returns an unmodifiable list' do
99
- expect { trace.events << :foo }.to raise_error
100
- end
101
-
102
- context 'returns a list of trace events whose' do
103
- let :events do
104
- trace.events
105
- end
106
-
107
- let :event do
108
- events.first
109
- end
110
-
111
- describe '#activity' do
112
- it 'returns the event activity' do
113
- event.activity.should == 'Parsing statement'
114
- end
115
- end
116
-
117
- describe '#source' do
118
- it 'returns the event source' do
119
- event.source.should == IPAddr.new('127.0.0.1')
120
- end
121
- end
122
-
123
- describe '#source_elapsed' do
124
- it 'returns the elapsed time at the source' do
125
- event.source_elapsed.should == 0.000052
126
- end
127
- end
128
-
129
- describe '#time' do
130
- it 'returns the time component from the event ID' do
131
- event.time.to_i.should == TimeUuid.new('a1028492-3f05-11e3-9531-fb72eff05fbb').to_time.to_i
132
- end
133
- end
134
- end
135
- end
136
- end
137
- end
138
- end