cql-rb 2.0.0.pre2 → 2.0.0.rc0

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