cql-rb 1.2.2 → 2.0.0.pre0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +4 -0
- data/README.md +139 -17
- data/lib/cql/client.rb +237 -8
- data/lib/cql/client/asynchronous_client.rb +138 -54
- data/lib/cql/client/asynchronous_prepared_statement.rb +41 -6
- data/lib/cql/client/authenticators.rb +46 -0
- data/lib/cql/client/batch.rb +115 -0
- data/lib/cql/client/connector.rb +255 -0
- data/lib/cql/client/execute_options_decoder.rb +25 -9
- data/lib/cql/client/keyspace_changer.rb +5 -5
- data/lib/cql/client/peer_discovery.rb +33 -0
- data/lib/cql/client/query_result.rb +124 -1
- data/lib/cql/client/request_runner.rb +4 -2
- data/lib/cql/client/synchronous_client.rb +14 -2
- data/lib/cql/client/synchronous_prepared_statement.rb +19 -1
- data/lib/cql/future.rb +97 -50
- data/lib/cql/io/connection.rb +0 -1
- data/lib/cql/io/io_reactor.rb +1 -1
- data/lib/cql/protocol.rb +8 -1
- data/lib/cql/protocol/cql_protocol_handler.rb +2 -2
- data/lib/cql/protocol/decoding.rb +10 -15
- data/lib/cql/protocol/frame_decoder.rb +2 -1
- data/lib/cql/protocol/frame_encoder.rb +5 -4
- data/lib/cql/protocol/requests/auth_response_request.rb +31 -0
- data/lib/cql/protocol/requests/batch_request.rb +59 -0
- data/lib/cql/protocol/requests/credentials_request.rb +1 -1
- data/lib/cql/protocol/requests/execute_request.rb +45 -17
- data/lib/cql/protocol/requests/options_request.rb +1 -1
- data/lib/cql/protocol/requests/prepare_request.rb +1 -1
- data/lib/cql/protocol/requests/query_request.rb +97 -5
- data/lib/cql/protocol/requests/register_request.rb +1 -1
- data/lib/cql/protocol/requests/startup_request.rb +4 -4
- data/lib/cql/protocol/response.rb +2 -2
- data/lib/cql/protocol/responses/auth_challenge_response.rb +25 -0
- data/lib/cql/protocol/responses/auth_success_response.rb +25 -0
- data/lib/cql/protocol/responses/authenticate_response.rb +1 -1
- data/lib/cql/protocol/responses/detailed_error_response.rb +1 -1
- data/lib/cql/protocol/responses/error_response.rb +3 -2
- data/lib/cql/protocol/responses/event_response.rb +3 -2
- data/lib/cql/protocol/responses/prepared_result_response.rb +10 -6
- data/lib/cql/protocol/responses/raw_rows_result_response.rb +27 -0
- data/lib/cql/protocol/responses/ready_response.rb +1 -1
- data/lib/cql/protocol/responses/result_response.rb +2 -2
- data/lib/cql/protocol/responses/rows_result_response.rb +43 -23
- data/lib/cql/protocol/responses/schema_change_event_response.rb +1 -1
- data/lib/cql/protocol/responses/schema_change_result_response.rb +1 -1
- data/lib/cql/protocol/responses/set_keyspace_result_response.rb +1 -1
- data/lib/cql/protocol/responses/status_change_event_response.rb +1 -1
- data/lib/cql/protocol/responses/supported_response.rb +1 -1
- data/lib/cql/protocol/responses/void_result_response.rb +1 -1
- data/lib/cql/protocol/type_converter.rb +2 -2
- data/lib/cql/uuid.rb +2 -2
- data/lib/cql/version.rb +1 -1
- data/spec/cql/client/asynchronous_client_spec.rb +493 -50
- data/spec/cql/client/asynchronous_prepared_statement_spec.rb +193 -11
- data/spec/cql/client/authenticators_spec.rb +56 -0
- data/spec/cql/client/batch_spec.rb +277 -0
- data/spec/cql/client/connector_spec.rb +606 -0
- data/spec/cql/client/execute_options_decoder_spec.rb +95 -0
- data/spec/cql/client/keyspace_changer_spec.rb +8 -8
- data/spec/cql/client/peer_discovery_spec.rb +92 -0
- data/spec/cql/client/query_result_spec.rb +352 -0
- data/spec/cql/client/request_runner_spec.rb +31 -5
- data/spec/cql/client/synchronous_client_spec.rb +44 -1
- data/spec/cql/client/synchronous_prepared_statement_spec.rb +63 -1
- data/spec/cql/future_spec.rb +50 -2
- data/spec/cql/protocol/cql_protocol_handler_spec.rb +16 -5
- data/spec/cql/protocol/decoding_spec.rb +16 -6
- data/spec/cql/protocol/encoding_spec.rb +3 -1
- data/spec/cql/protocol/frame_encoder_spec.rb +99 -50
- data/spec/cql/protocol/requests/auth_response_request_spec.rb +62 -0
- data/spec/cql/protocol/requests/batch_request_spec.rb +155 -0
- data/spec/cql/protocol/requests/credentials_request_spec.rb +1 -1
- data/spec/cql/protocol/requests/execute_request_spec.rb +184 -71
- data/spec/cql/protocol/requests/options_request_spec.rb +1 -1
- data/spec/cql/protocol/requests/prepare_request_spec.rb +1 -1
- data/spec/cql/protocol/requests/query_request_spec.rb +255 -32
- data/spec/cql/protocol/requests/register_request_spec.rb +1 -1
- data/spec/cql/protocol/requests/startup_request_spec.rb +12 -6
- data/spec/cql/protocol/responses/auth_challenge_response_spec.rb +31 -0
- data/spec/cql/protocol/responses/auth_success_response_spec.rb +31 -0
- data/spec/cql/protocol/responses/authenticate_response_spec.rb +2 -1
- data/spec/cql/protocol/responses/detailed_error_response_spec.rb +14 -7
- data/spec/cql/protocol/responses/error_response_spec.rb +4 -2
- data/spec/cql/protocol/responses/event_response_spec.rb +7 -4
- data/spec/cql/protocol/responses/prepared_result_response_spec.rb +89 -34
- data/spec/cql/protocol/responses/raw_rows_result_response_spec.rb +66 -0
- data/spec/cql/protocol/responses/ready_response_spec.rb +1 -1
- data/spec/cql/protocol/responses/result_response_spec.rb +19 -7
- data/spec/cql/protocol/responses/rows_result_response_spec.rb +56 -11
- data/spec/cql/protocol/responses/schema_change_event_response_spec.rb +2 -1
- data/spec/cql/protocol/responses/schema_change_result_response_spec.rb +2 -1
- data/spec/cql/protocol/responses/set_keyspace_result_response_spec.rb +1 -1
- data/spec/cql/protocol/responses/status_change_event_response_spec.rb +2 -1
- data/spec/cql/protocol/responses/supported_response_spec.rb +2 -1
- data/spec/cql/protocol/responses/topology_change_event_response_spec.rb +2 -1
- data/spec/cql/protocol/responses/void_result_response_spec.rb +1 -1
- data/spec/cql/protocol/type_converter_spec.rb +21 -4
- data/spec/cql/uuid_spec.rb +10 -3
- data/spec/integration/client_spec.rb +251 -28
- data/spec/integration/protocol_spec.rb +213 -62
- data/spec/integration/regression_spec.rb +4 -1
- data/spec/integration/uuid_spec.rb +4 -1
- data/spec/support/fake_io_reactor.rb +5 -5
- metadata +36 -7
- data/lib/cql/client/connection_helper.rb +0 -181
- data/spec/cql/client/connection_helper_spec.rb +0 -429
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1b00538d250d975f9b1ed2795bf947d9baf97c35
|
4
|
+
data.tar.gz: e1b27e7d7665bb8545b14c088fb7076ab5e76b01
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9b24cfa7ecbd973138d21c64e6f64ab02673deefc8f9bab46e0cc1658d041bf15b237326aaec3cfd6f83f8152a802d4efed71cef505fc3fe6102d3d87c7fd49e
|
7
|
+
data.tar.gz: b3a5f99e920989e64ebde8669c3339890ee6b8bdd0c882e65ea2354d0055bd29bd48f2089355d13c279b64379fbb258cf0987306b6e9b11c49caec51d86adc54
|
data/.yardopts
ADDED
data/README.md
CHANGED
@@ -2,20 +2,21 @@
|
|
2
2
|
|
3
3
|
[![Build Status](https://travis-ci.org/iconara/cql-rb.png?branch=master)](https://travis-ci.org/iconara/cql-rb)
|
4
4
|
[![Coverage Status](https://coveralls.io/repos/iconara/cql-rb/badge.png)](https://coveralls.io/r/iconara/cql-rb)
|
5
|
+
[![Blog](http://b.repl.ca/v1/blog-cqlrb-ff69b4.png)](http://architecturalatrocities.com/tagged/cqlrb)
|
5
6
|
|
6
|
-
|
7
|
+
_If you're reading this on GitHub, please note that this is the readme for the development version and that some features described here might not yet have been released. You can find the readme for a specific version either through [rubydoc.info](http://rubydoc.info/find/gems?q=cql-rb) or via the release tags ([here is an example](https://github.com/iconara/cql-rb/tree/v1.2.0))._
|
7
8
|
|
8
9
|
# Requirements
|
9
10
|
|
10
|
-
Cassandra 1.2 or later with the native transport protocol turned on and a modern Ruby. It's tested continuously
|
11
|
+
Cassandra 1.2 or later with the native transport protocol turned on and a modern Ruby. It's [tested continuously using Travis](https://travis-ci.org/iconara/cql-rb) with Cassandra 2.0.5, Ruby 1.9.3, 2.0, JRuby 1.7 and Rubinius 2.1.
|
11
12
|
|
12
13
|
# Installation
|
13
14
|
|
14
15
|
gem install cql-rb
|
15
16
|
|
16
|
-
|
17
|
+
if you want to use compression you should also install the [snappy gem](http://rubygems.org/gems/snappy):
|
17
18
|
|
18
|
-
|
19
|
+
gem install snappy
|
19
20
|
|
20
21
|
# Quick start
|
21
22
|
|
@@ -40,7 +41,7 @@ client.close
|
|
40
41
|
|
41
42
|
# Usage
|
42
43
|
|
43
|
-
The full [API documentation]
|
44
|
+
The full [API documentation][1] is available from [rubydoc.info](http://rubydoc.info/).
|
44
45
|
|
45
46
|
## Changing keyspaces
|
46
47
|
|
@@ -113,7 +114,7 @@ client.use('measurements')
|
|
113
114
|
client.execute(table_definition)
|
114
115
|
```
|
115
116
|
|
116
|
-
You can also `ALTER` keyspaces and tables, and you can read more about that in the [CQL3 syntax documentation]
|
117
|
+
You can also `ALTER` keyspaces and tables, and you can read more about that in the [CQL3 syntax documentation][2].
|
117
118
|
|
118
119
|
## Prepared statements
|
119
120
|
|
@@ -130,6 +131,94 @@ A prepared statement can be run many times, but the CQL parsing will only be don
|
|
130
131
|
|
131
132
|
Statements are prepared on all connections and each call to `#execute` selects a random connection to run the query on.
|
132
133
|
|
134
|
+
## Batching
|
135
|
+
|
136
|
+
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.
|
137
|
+
|
138
|
+
There are a few different ways to work with batches, one is with a block where you build up a batch that is sent when the block ends:
|
139
|
+
|
140
|
+
```ruby
|
141
|
+
client.batch do |batch|
|
142
|
+
batch.add("UPDATE users SET name = 'Sue' WHERE user_id = 'unicorn31'")
|
143
|
+
batch.add("UPDATE users SET name = 'Kim' WHERE user_id = 'dudezor13'")
|
144
|
+
batch.add("UPDATE users SET name = 'Jim' WHERE user_id = 'kittenz98'")
|
145
|
+
end
|
146
|
+
```
|
147
|
+
|
148
|
+
Another is by creating a batch and sending it yourself:
|
149
|
+
|
150
|
+
```ruby
|
151
|
+
batch = client.batch
|
152
|
+
batch.add("UPDATE users SET name = 'Sue' WHERE user_id = 'unicorn31'")
|
153
|
+
batch.add("UPDATE users SET name = 'Kim' WHERE user_id = 'dudezor13'")
|
154
|
+
batch.add("UPDATE users SET name = 'Jim' WHERE user_id = 'kittenz98'")
|
155
|
+
batch.execute
|
156
|
+
```
|
157
|
+
|
158
|
+
You can mix any combination of statements in a batch:
|
159
|
+
|
160
|
+
```ruby
|
161
|
+
prepared_statement = client.prepare("UPDATE users SET name = ? WHERE user_id = ?")
|
162
|
+
client.batch do |batch|
|
163
|
+
batch.add(prepared_statement, 'Sue', 'unicorn31')
|
164
|
+
batch.add("UPDATE users SET age = 19 WHERE user_id = 'unicorn31'")
|
165
|
+
batch.add("INSERT INTO activity (user_id, what, when) VALUES (?, 'login', NOW())", 'unicorn31')
|
166
|
+
end
|
167
|
+
```
|
168
|
+
|
169
|
+
Batches can have one of three different types: `logged`, `unlogged` or `counter`, where `logged` is the default. Their exact semantics are defined in the [Cassandra documentation][3], but this is how you specify which one you want:
|
170
|
+
|
171
|
+
```ruby
|
172
|
+
counter_statement = client.prepare("UPDATE my_counter_table SET my_counter = my_counter + ? WHERE id = ?")
|
173
|
+
client.batch(:counter) do |batch|
|
174
|
+
batch.add(counter_statement, 3, 'some_counter')
|
175
|
+
batch.add(counter_statement, 2, 'another_counter')
|
176
|
+
end
|
177
|
+
```
|
178
|
+
|
179
|
+
You can also specify the regular options such as consistency, timeout and whether or not to enable tracing:
|
180
|
+
|
181
|
+
```ruby
|
182
|
+
client.batch(:unlogged, trace: true) do |batch|
|
183
|
+
# ...
|
184
|
+
end
|
185
|
+
|
186
|
+
client.batch(trace: true, consistency: :all) do |batch|
|
187
|
+
# ...
|
188
|
+
end
|
189
|
+
|
190
|
+
batch = client.batch
|
191
|
+
# ...
|
192
|
+
batch.execute(consistency: :quorum)
|
193
|
+
|
194
|
+
batch = client.batch(trace: true)
|
195
|
+
# ...
|
196
|
+
batch.execute(consistency: :quorum)
|
197
|
+
```
|
198
|
+
|
199
|
+
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.
|
200
|
+
|
201
|
+
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.
|
202
|
+
|
203
|
+
## Paging
|
204
|
+
|
205
|
+
If you're using Cassandra 2.0 or later you can page your query results by adding the `:page_size` option to a query:
|
206
|
+
|
207
|
+
```ruby
|
208
|
+
result_page = client.execute("SELECT * FROM large_table WHERE id = 'partition_with_lots_of_data'", page_size: 100)
|
209
|
+
|
210
|
+
loop do
|
211
|
+
result_page.each do |row|
|
212
|
+
p row
|
213
|
+
end
|
214
|
+
if result_page.last?
|
215
|
+
break
|
216
|
+
else
|
217
|
+
result_page = result_page.next_page
|
218
|
+
end
|
219
|
+
end
|
220
|
+
```
|
221
|
+
|
133
222
|
## Consistency
|
134
223
|
|
135
224
|
You can specify the default consistency to use when you create a new `Client`:
|
@@ -179,17 +268,37 @@ compressor = Cql::Compression::SnappyCompressor.new
|
|
179
268
|
client = Cql::Client.connect(hosts: %w[localhost], compressor: compressor)
|
180
269
|
```
|
181
270
|
|
271
|
+
## Logging
|
272
|
+
|
273
|
+
You can pass a standard Ruby logger to the client to get some more information about what is going on:
|
274
|
+
|
275
|
+
```ruby
|
276
|
+
require 'logger'
|
277
|
+
|
278
|
+
client = Cql::Client.connect(logger: Logger.new($stderr))
|
279
|
+
```
|
280
|
+
|
281
|
+
Most of the logging will be when the driver connects and discovers new nodes, when connections fail and so on, but also when statements are prepared. The logging is designed to not cause much overhead and only relatively rare events are logged (e.g. normal requests are not logged).
|
282
|
+
|
283
|
+
## Thread safety
|
284
|
+
|
285
|
+
Except for results and batches everything in cql-rb is thread safe. You only need a single client object in your application, in fact creating more than one is a bad idea. Similarily prepared statements are thread safe and should be shared.
|
286
|
+
|
287
|
+
There are two things that you should be aware are not thread safe: result objects and batches. Result objects are wrappers around an array of rows and their primary use case is iteration, something that makes little sense to do concurrently. Because of this they've been designed to not be thread safe to avoid the unnecessary cost of locking. Similarily it creating batches aren't usually built concurrently, so to avoid the cost of locking they are not thread safe. If you, for some reason, need to use results or batches concurrently, you're responsible for locking around them. If you do this, you're probably doing something wrong, though.
|
288
|
+
|
182
289
|
# CQL3
|
183
290
|
|
184
291
|
This is just a driver for the Cassandra native CQL protocol, it doesn't really know anything about CQL. You can run any CQL3 statement and the driver will return whatever Cassandra replies with.
|
185
292
|
|
186
|
-
Read more about CQL3 in the [CQL3 syntax documentation]
|
293
|
+
Read more about CQL3 in the [CQL3 syntax documentation][2] and the [Cassandra query documentation][3].
|
187
294
|
|
188
|
-
#
|
295
|
+
# Troubleshooting
|
189
296
|
|
190
|
-
|
297
|
+
## I get "connection refused" errors
|
191
298
|
|
192
|
-
|
299
|
+
Make sure that the native transport protocol is enabled. If you're running Cassandra 1.2.5 or later the native transport protocol is enabled by default, if you're running an earlier version (but later than 1.2) you must enable it by editing `cassandra.yaml` and setting `start_native_transport` to `true`.
|
300
|
+
|
301
|
+
To verify that the native transport protocol is enabled, search your logs for the message "Starting listening for CQL clients" and look at which IP and port it is binding to.
|
193
302
|
|
194
303
|
## I get "Deadlock detected" errors
|
195
304
|
|
@@ -227,15 +336,19 @@ If your process does not fork and you still encounter deadlock errors, it might
|
|
227
336
|
|
228
337
|
If you're using cql-rb on Windows there's an [experimental branch with Windows support](https://github.com/iconara/cql-rb/tree/windows_support). The problem is that Windows does not support non blocking reads on IO objects other than sockets, and the fix is very small. Unfortunately I have no way of properly testing things in Windows, so therefore the "experimental" label.
|
229
338
|
|
339
|
+
## I get `QueryError`
|
340
|
+
|
341
|
+
All errors that originate on the server side are raised as `QueryError`. If you get one of these the error is in your CQL or on the server side.
|
342
|
+
|
230
343
|
## I'm not getting all elements back from my list/set/map
|
231
344
|
|
232
345
|
There's a known issue with collections that get too big. The protocol uses a short for the size of collections, but there is no way for Cassandra to stop you from creating a collection bigger than 65536 elements, so when you do the size field overflows with strange results. The data is there, you just can't get it back.
|
233
346
|
|
234
347
|
## Authentication doesn't work
|
235
348
|
|
236
|
-
Please open an issue. It should be working, but it's hard to write tests for, so there may be edge cases that aren't covered.
|
349
|
+
Please open an issue. It should be working, but it's hard to set up and write automated tests for, so there may be edge cases that aren't covered. If you're using Cassandra 2.0 or DataStax Enterprise 4.0 or higher and are using something other than the built in `PasswordAuthenticator` your setup is theoretically supported, but it's not field tested.
|
237
350
|
|
238
|
-
If you are using DataStax Enterprise
|
351
|
+
If you are using DataStax Enterprise earlier than 4.0 authentication is unfortunately supported. Please open an issue and we might be able to get it working, I just need someone who's willing to test it out. DataStax backported the authentication from Cassandra 2.0 into DSE, even though it only uses Cassandra 1.2. The authentication logic might not be able to handle this and will try to authenticate with DSE using an earlier version of the protocol. In short, DSE before 4.0 uses a non-standard protocol, but it should be possible to get it working.
|
239
352
|
|
240
353
|
## I'm connecting to port 9160 and it doesn't work
|
241
354
|
|
@@ -259,9 +372,13 @@ Applications using cql-rb and JRuby can do over 10,000 write requests per second
|
|
259
372
|
|
260
373
|
## Try batching
|
261
374
|
|
262
|
-
Batching in Cassandra isn't always as good as in other (non-distributed) databases. Since rows are distributed accross the cluster the coordinator node must still send the individual pieces of a batch to other nodes, and you could have done that yourself instead.
|
375
|
+
Batching in Cassandra isn't always as good as in other (non-distributed) databases. Since rows are distributed accross the cluster the coordinator node must still send the individual pieces of a batch to other nodes, and you could have done that yourself instead.
|
376
|
+
|
377
|
+
For Cassandra 1.2 it is often best not to use batching at all, you'll have to smash strings together to create the batch statements, and that will waste time on the client side, will take longer to push over the network, and will take longer to parse and process on the server side. Prepared statements are almost always a better choice.
|
263
378
|
|
264
|
-
Cassandra 2.0 introduced a new form of batches where you can send a batch of prepared statement executions as one request,
|
379
|
+
Cassandra 2.0 introduced a new form of batches where you can send a batch of prepared statement executions as one request (you can send non-prepared statements too, but we're talking performance here). These bring the best of both worlds and can be beneficial for some use cases. Some of the same caveats still apply though and you should test it for your use case.
|
380
|
+
|
381
|
+
Whenever you use batching, try compression too.
|
265
382
|
|
266
383
|
## Try compression
|
267
384
|
|
@@ -269,6 +386,8 @@ If your requests or responses are big, compression can help decrease the amound
|
|
269
386
|
|
270
387
|
In read-heavy applications requests are often small, and need no compression, but responses can be big. In these situations you can modify the compressor used to turn off compression for requests completely. The Snappy compressor that comes with cql-rb will not compress frames less than 64 bytes, for example, and you can change this threshold when you create the compressor.
|
271
388
|
|
389
|
+
Compression works best for large requests, so if you use batching you should benchmark if compression gives you a speed boost.
|
390
|
+
|
272
391
|
# Try experimental features
|
273
392
|
|
274
393
|
To get maximum performance you can't wait for a request to complete before sending the next. At it's core cql-rb embraces this completely and uses non-blocking IO and a completely asynchronous model for the request processing. The synchronous API that you use is just a thin façade on top that exists for convenience. If you need to scale to thousands of requests per second, have a look at the client code and look at the asynchronous core, it works very much like the public API, _but using it they should be considererd **experimental**_. Experimental in this context does not mean buggy, it is the core of cql-rb after all, but it means that you cannot rely on it being backwards compatible.
|
@@ -277,7 +396,7 @@ To get maximum performance you can't wait for a request to complete before sendi
|
|
277
396
|
|
278
397
|
Check out the [releases on GitHub](https://github.com/iconara/cql-rb/releases). Version numbering follows the [semantic versioning](http://semver.org/) scheme.
|
279
398
|
|
280
|
-
Private and experimental APIs, defined as whatever is not in the [public API documentation]
|
399
|
+
Private and experimental APIs, defined as whatever is not in the [public API documentation][1], i.e. classes and methods marked as `@private`, will change without warning. If you've been recommended to try an experimental API by the maintainers, please let them know if you depend on that API. Experimental APIs will eventually become public, and knowing how they are used helps in determining their maturity.
|
281
400
|
|
282
401
|
Prereleases will be stable, in the sense that they will have finished and properly tested features only, but may introduce APIs that will change before the final release. Please use the prereleases and report bugs, but don't deploy them to production without consulting the maintainers, or doing extensive testing yourself. If you do deploy to production please let the maintainers know as this helps determining the maturity of the release.
|
283
402
|
|
@@ -287,7 +406,6 @@ Prereleases will be stable, in the sense that they will have finished and proper
|
|
287
406
|
* Windows is not supported (there is experimental support in the [`windows` branch](https://github.com/iconara/cql-rb/tree/windows_support)).
|
288
407
|
* Large results are buffered in memory until the whole response has been loaded, the protocol makes it possible to start to deliver rows to the client code as soon as the metadata is loaded, but this is not supported yet.
|
289
408
|
* There is no cluster introspection utilities (like the `DESCRIBE` commands in `cqlsh`) -- but it's not clear whether that will ever be added, it would be useful, but it is also something that another gem could add on top.
|
290
|
-
* New features in v2 of the protocol are not supported -- this is planned and in progress
|
291
409
|
|
292
410
|
Also check out the [issues](https://github.com/iconara/cql-rb/issues) for open bugs.
|
293
411
|
|
@@ -303,4 +421,8 @@ _Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
|
303
421
|
|
304
422
|
[http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
305
423
|
|
306
|
-
_Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License._
|
424
|
+
_Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License._
|
425
|
+
|
426
|
+
[1]: http://rubydoc.info/github/iconara/cql-rb/frames
|
427
|
+
[2]: https://github.com/apache/cassandra/blob/cassandra-2.0/doc/cql3/CQL.textile
|
428
|
+
[3]: http://www.datastax.com/documentation/cql/3.1/webhelp/index.html
|
data/lib/cql/client.rb
CHANGED
@@ -21,6 +21,8 @@ module Cql
|
|
21
21
|
ClientError = Class.new(CqlError)
|
22
22
|
AuthenticationError = Class.new(ClientError)
|
23
23
|
IncompleteTraceError = Class.new(ClientError)
|
24
|
+
UnsupportedProtocolVersionError = Class.new(ClientError)
|
25
|
+
NotPreparedError = Class.new(ClientError)
|
24
26
|
|
25
27
|
# A CQL client manages connections to one or more Cassandra nodes and you use
|
26
28
|
# it run queries, insert and update data.
|
@@ -83,6 +85,12 @@ module Cql
|
|
83
85
|
# connection, in seconds.
|
84
86
|
# @option options [String] :keyspace The keyspace to change to immediately
|
85
87
|
# after all connections have been established, this is optional.
|
88
|
+
# @option options [Hash] :credentials When using Cassandra's built in
|
89
|
+
# authentication you can provide your username and password through this
|
90
|
+
# option. Example: `:credentials => {:username => 'cassandra', :password => 'cassandra'}`
|
91
|
+
# @option options [Object] :auth_provider When using custom authentication
|
92
|
+
# use this option to specify the auth provider that will handle the
|
93
|
+
# authentication negotiation. See {Cql::Client::AuthProvider} for more info.
|
86
94
|
# @option options [Integer] :connections_per_node (1) The number of
|
87
95
|
# connections to open to each node. Each connection can have 128
|
88
96
|
# concurrent requests, so unless you have a need for more than that (times
|
@@ -95,12 +103,16 @@ module Cql
|
|
95
103
|
# compression will be enabled. If the server does not support compression
|
96
104
|
# or the specific compression algorithm specified by the compressor,
|
97
105
|
# compression will not be enabled and a warning will be logged.
|
106
|
+
# @option options [String] :cql_version Specifies which CQL version the
|
107
|
+
# server should expect.
|
98
108
|
# @option options [Integer] :logger If you want the client to log
|
99
109
|
# significant events pass an object implementing the standard Ruby logger
|
100
110
|
# interface (e.g. quacks like `Logger` from the standard library) with
|
101
111
|
# this option.
|
102
112
|
# @raise Cql::Io::ConnectionError when a connection couldn't be established
|
103
113
|
# to any node
|
114
|
+
# @raise Cql::Client::QueryError when the specified keyspace does not exist
|
115
|
+
# or when the specifed CQL version is not supported.
|
104
116
|
# @return [Cql::Client::Client]
|
105
117
|
def self.connect(options={})
|
106
118
|
SynchronousClient.new(AsynchronousClient.new(options)).connect
|
@@ -147,9 +159,19 @@ module Cql
|
|
147
159
|
# @raise [Cql::NotConnectedError] raised when the client is not connected
|
148
160
|
# @return [nil]
|
149
161
|
|
150
|
-
# @!method execute(cql, options_or_consistency=
|
162
|
+
# @!method execute(cql, *values, options_or_consistency={})
|
151
163
|
#
|
152
|
-
# Execute a CQL statement.
|
164
|
+
# Execute a CQL statement, optionally passing bound values.
|
165
|
+
#
|
166
|
+
# When passing bound values the request encoder will have to guess what
|
167
|
+
# types to encode the values as. For most types this will be no problem,
|
168
|
+
# but for integers and floating point numbers the larger size will be
|
169
|
+
# chosen (e.g. `BIGINT` and `DOUBLE` and not `INT` and `FLOAT`). You can
|
170
|
+
# override the guessing with the `:type_hint` option. Don't use on-the-fly
|
171
|
+
# bound values when you will issue the request multiple times, prepared
|
172
|
+
# statements are almost always a better choice.
|
173
|
+
#
|
174
|
+
# @note On-the-fly bound values are not supported in Cassandra 1.2
|
153
175
|
#
|
154
176
|
# @example A simple CQL query
|
155
177
|
# result = client.execute("SELECT * FROM users WHERE user_name = 'sue'")
|
@@ -157,6 +179,12 @@ module Cql
|
|
157
179
|
# p row
|
158
180
|
# end
|
159
181
|
#
|
182
|
+
# @example Using on-the-fly bound values
|
183
|
+
# client.execute('INSERT INTO users (user_name, full_name) VALUES (?, ?)', 'sue', 'Sue Smith')
|
184
|
+
#
|
185
|
+
# @example Using on-the-fly bound values with type hints
|
186
|
+
# client.execute('INSERT INTO users (user_name, age) VALUES (?, ?)', 'sue', 33, type_hints: [nil, :int])
|
187
|
+
#
|
160
188
|
# @example Specifying the consistency as a symbol
|
161
189
|
# client.execute("UPDATE users SET full_name = 'Sue S. Smith' WHERE user_name = 'sue'", consistency: :one)
|
162
190
|
#
|
@@ -168,17 +196,39 @@ module Cql
|
|
168
196
|
# p result.trace_id
|
169
197
|
#
|
170
198
|
# @param [String] cql
|
199
|
+
# @param [Array] values Values to bind to any binding markers in the
|
200
|
+
# query (i.e. "?" placeholders) -- using this feature is similar to
|
201
|
+
# using a prepared statement, but without the type checking. The client
|
202
|
+
# needs to guess which data types to encode the values as, and will err
|
203
|
+
# on the side of caution, using types like BIGINT instead of INT for
|
204
|
+
# integers, and DOUBLE instead of FLOAT for floating point numbers. It
|
205
|
+
# is not recommended to use this feature for anything but convenience,
|
206
|
+
# and the algorithm used to guess types is to be considered experimental.
|
171
207
|
# @param [Hash] options_or_consistency Either a consistency as a symbol
|
172
208
|
# (e.g. `:quorum`), or a options hash (see below). Passing a symbol is
|
173
209
|
# equivalent to passing the options `consistency: <symbol>`.
|
174
210
|
# @option options_or_consistency [Symbol] :consistency (:quorum) The
|
175
211
|
# consistency to use for this query.
|
176
|
-
# @option options_or_consistency [Symbol] :
|
212
|
+
# @option options_or_consistency [Symbol] :serial_consistency (nil) The
|
213
|
+
# consistency to use for conditional updates (`:serial` or
|
214
|
+
# `:local_serial`), see the CQL documentation for the semantics of
|
215
|
+
# serial consistencies and conditional updates. The default is assumed
|
216
|
+
# to be `:serial` by the server if none is specified. Ignored for non-
|
217
|
+
# conditional queries.
|
218
|
+
# @option options_or_consistency [Integer] :timeout (nil) How long to wait
|
177
219
|
# for a response. If this timeout expires a {Cql::TimeoutError} will
|
178
220
|
# be raised.
|
179
|
-
# @option options_or_consistency [
|
221
|
+
# @option options_or_consistency [Boolean] :trace (false) Request tracing
|
180
222
|
# for this request. See {Cql::Client::QueryResult} and
|
181
223
|
# {Cql::Client::VoidResult} for how to retrieve the tracing data.
|
224
|
+
# @option options_or_consistency [Array] :type_hints (nil) When passing
|
225
|
+
# on-the-fly bound values the request encoder will have to guess what
|
226
|
+
# types to encode the values as. Using this option you can give it hints
|
227
|
+
# and avoid it guessing wrong. The hints must be an array that has the
|
228
|
+
# same number of arguments as the number of bound values, and each
|
229
|
+
# element should be the type of the corresponding value, or nil if you
|
230
|
+
# prefer the encoder to guess. The types should be provided as lower
|
231
|
+
# case symbols, e.g. `:int`, `:time_uuid`, etc.
|
182
232
|
# @raise [Cql::NotConnectedError] raised when the client is not connected
|
183
233
|
# @raise [Cql::TimeoutError] raised when a timeout was specified and no
|
184
234
|
# response was received within the timeout.
|
@@ -204,12 +254,64 @@ module Cql
|
|
204
254
|
# side, for example when you specify a malformed CQL query
|
205
255
|
# @return [Cql::Client::PreparedStatement] an object encapsulating the
|
206
256
|
# prepared statement
|
257
|
+
|
258
|
+
# @!method batch(type=:logged, options={})
|
259
|
+
#
|
260
|
+
# Yields a batch when called with a block. The batch is automatically
|
261
|
+
# executed at the end of the block and the result is returned.
|
262
|
+
#
|
263
|
+
# Returns a batch when called wihtout a block. The batch will remember
|
264
|
+
# the options given and merge these with any additional options given
|
265
|
+
# when {Cql::Client::Batch#execute} is called.
|
266
|
+
#
|
267
|
+
# Please note that the batch object returned by this method _is not thread
|
268
|
+
# safe_.
|
269
|
+
#
|
270
|
+
# The type parameter can be ommitted and the options can then be given
|
271
|
+
# as first parameter.
|
272
|
+
#
|
273
|
+
# @example Executing queries in a batch
|
274
|
+
# client.batch do |batch|
|
275
|
+
# batch.add(%(INSERT INTO metrics (id, time, value) VALUES (1234, NOW(), 23423)))
|
276
|
+
# batch.add(%(INSERT INTO metrics (id, time, value) VALUES (2346, NOW(), 13)))
|
277
|
+
# batch.add(%(INSERT INTO metrics (id, time, value) VALUES (2342, NOW(), 2367)))
|
278
|
+
# batch.add(%(INSERT INTO metrics (id, time, value) VALUES (4562, NOW(), 1231)))
|
279
|
+
# end
|
280
|
+
#
|
281
|
+
# @example Using the returned batch object
|
282
|
+
# batch = client.batch(:counter, trace: true)
|
283
|
+
# batch.add('UPDATE counts SET value = value + ? WHERE id = ?', 4, 87654)
|
284
|
+
# batch.add('UPDATE counts SET value = value + ? WHERE id = ?', 3, 6572)
|
285
|
+
# result = batch.execute(timeout: 10)
|
286
|
+
# puts result.trace_id
|
287
|
+
#
|
288
|
+
# @example Providing type hints for on-the-fly bound values
|
289
|
+
# batch = client.batch
|
290
|
+
# batch.add('UPDATE counts SET value = value + ? WHERE id = ?', 4, type_hints: [:int])
|
291
|
+
# batch.execute
|
292
|
+
#
|
293
|
+
# @see Cql::Client::Batch
|
294
|
+
# @param [Symbol] type the type of batch, must be one of `:logged`,
|
295
|
+
# `:unlogged` and `:counter`. The precise meaning of these is defined
|
296
|
+
# in the CQL specification.
|
297
|
+
# @yieldparam [Cql::Client::Batch] batch the batch
|
298
|
+
# @return [Cql::Client::VoidResult, Cql::Client::Batch] when no block is
|
299
|
+
# given the batch is returned, when a block is given the result of
|
300
|
+
# executing the batch is returned (see {Cql::Client::Batch#execute}).
|
207
301
|
end
|
208
302
|
|
209
303
|
class PreparedStatement
|
304
|
+
# Metadata describing the bound values
|
305
|
+
#
|
210
306
|
# @return [ResultMetadata]
|
211
307
|
attr_reader :metadata
|
212
308
|
|
309
|
+
# Metadata about the result (i.e. rows) that is returned when executing
|
310
|
+
# this prepared statement.
|
311
|
+
#
|
312
|
+
# @return [ResultMetadata]
|
313
|
+
attr_reader :result_metadata
|
314
|
+
|
213
315
|
# Execute the prepared statement with a list of values to be bound to the
|
214
316
|
# statements parameters.
|
215
317
|
#
|
@@ -242,16 +344,138 @@ module Cql
|
|
242
344
|
def execute(*args)
|
243
345
|
end
|
244
346
|
end
|
347
|
+
|
348
|
+
class Batch
|
349
|
+
# @!method add(cql_or_prepared_statement, *bound_values)
|
350
|
+
#
|
351
|
+
# Add a query or a prepared statement to the batch.
|
352
|
+
#
|
353
|
+
# @example Adding a mix of statements to a batch
|
354
|
+
# batch.add(%(UPDATE people SET name = 'Miriam' WHERE id = 3435))
|
355
|
+
# batch.add(%(UPDATE people SET name = ? WHERE id = ?), 'Miriam', 3435)
|
356
|
+
# batch.add(prepared_statement, 'Miriam', 3435)
|
357
|
+
#
|
358
|
+
# @param [String, Cql::Client::PreparedStatement] cql_or_prepared_statement
|
359
|
+
# a CQL string or a prepared statement object (obtained through
|
360
|
+
# {Cql::Client::Client#prepare})
|
361
|
+
# @param [Array] bound_values a list of bound values -- only applies when
|
362
|
+
# adding prepared statements and when there are binding markers in the
|
363
|
+
# given CQL. If the last argument is a hash and it has the key
|
364
|
+
# `:type_hints` this will be passed as type hints to the request encoder
|
365
|
+
# (if the last argument is any other hash it will be assumed to be a
|
366
|
+
# bound value of type MAP). See {Cql::Client::Client#execute} for more
|
367
|
+
# info on type hints.
|
368
|
+
# @return [nil]
|
369
|
+
|
370
|
+
# @!method execute(options={})
|
371
|
+
#
|
372
|
+
# Execute the batch and return the result.
|
373
|
+
#
|
374
|
+
# @param options [Hash] an options hash or a symbol (as a shortcut for
|
375
|
+
# specifying the consistency), see {Cql::Client::Client#execute} for
|
376
|
+
# full details about how this value is interpreted.
|
377
|
+
# @raise [Cql::QueryError] raised when there is an error on the server side
|
378
|
+
# @raise [Cql::NotPreparedError] raised in the unlikely event that a
|
379
|
+
# prepared statement was not prepared on the chosen connection
|
380
|
+
# @return [Cql::Client::VoidResult] a batch always returns a void result
|
381
|
+
end
|
382
|
+
|
383
|
+
class PreparedStatementBatch
|
384
|
+
# @!method add(*bound_values)
|
385
|
+
#
|
386
|
+
# Add the statement to the batch with the specified bound values.
|
387
|
+
#
|
388
|
+
# @param [Array] bound_values the values to bind to the added statement,
|
389
|
+
# see {Cql::Client::PreparedStatement#execute}.
|
390
|
+
# @return [nil]
|
391
|
+
|
392
|
+
# @!method execute(options={})
|
393
|
+
#
|
394
|
+
# Execute the batch and return the result.
|
395
|
+
#
|
396
|
+
# @raise [Cql::QueryError] raised when there is an error on the server side
|
397
|
+
# @raise [Cql::NotPreparedError] raised in the unlikely event that a
|
398
|
+
# prepared statement was not prepared on the chosen connection
|
399
|
+
# @return [Cql::Client::VoidResult] a batch always returns a void result
|
400
|
+
end
|
401
|
+
|
402
|
+
# An auth provider is a factory for {Cql::Client::Authenticator} instances
|
403
|
+
# (or objects matching that interface). Its {#create_authenticator} will be
|
404
|
+
# called once for each connection that requires authentication.
|
405
|
+
#
|
406
|
+
# If the authentication requires keeping state, keep that in the
|
407
|
+
# authenticator instances, not in the auth provider.
|
408
|
+
#
|
409
|
+
# @note Creating an authenticator must absolutely not block, or the whole
|
410
|
+
# connection process will block.
|
411
|
+
#
|
412
|
+
# @note Auth providers given to {Cql::Client.connect} as the `:auth_provider`
|
413
|
+
# option don't need to be subclasses of this class, but need to
|
414
|
+
# implement the same methods. This class exists only for documentation
|
415
|
+
# purposes.
|
416
|
+
class AuthProvider
|
417
|
+
# @!method create_authenticator(authentication_class, protocol_version)
|
418
|
+
#
|
419
|
+
# Create a new authenticator object. This method will be called once per
|
420
|
+
# connection that requires authentication. The auth provider can create
|
421
|
+
# different authenticators for different authentication classes, or return
|
422
|
+
# nil if it does not support the authentication class.
|
423
|
+
#
|
424
|
+
# @note This method must absolutely not block.
|
425
|
+
#
|
426
|
+
# @param authentication_class [String] the authentication class used by
|
427
|
+
# the server.
|
428
|
+
# @return [Cql::Client::Authenticator, nil] an object with an interface
|
429
|
+
# matching {Cql::Client::Authenticator} or nil if the authentication
|
430
|
+
# class is not supported.
|
431
|
+
end
|
432
|
+
|
433
|
+
# An authenticator handles the authentication challenge/response cycles of
|
434
|
+
# a single connection. It can be stateful, but it must not for any reason
|
435
|
+
# block. If any of the method calls block, the whole connection process
|
436
|
+
# will be blocked.
|
437
|
+
#
|
438
|
+
# @note Authenticators created by auth providers don't need to be subclasses
|
439
|
+
# of this class, but need to implement the same methods. This class exists
|
440
|
+
# only for documentation purposes.
|
441
|
+
class Authenticator
|
442
|
+
# @!method initial_response
|
443
|
+
#
|
444
|
+
# This method must return the initial authentication token to be sent to
|
445
|
+
# the server.
|
446
|
+
#
|
447
|
+
# @note This method must absolutely not block.
|
448
|
+
#
|
449
|
+
# @return [String] the initial authentication token
|
450
|
+
|
451
|
+
# @!method challenge_response(token)
|
452
|
+
#
|
453
|
+
# If the authentication requires multiple challenge/response cycles this
|
454
|
+
# method will be called when a challenge is returned by the server. A
|
455
|
+
# response token must be created and will be sent back to the server.
|
456
|
+
#
|
457
|
+
# @note This method must absolutely not block.
|
458
|
+
#
|
459
|
+
# @param token [String] a challenge token sent by the server
|
460
|
+
# @return [String] the authentication token to send back to the server
|
461
|
+
|
462
|
+
# @!method authentication_successful(token)
|
463
|
+
#
|
464
|
+
# Called when the authentication is successful.
|
465
|
+
#
|
466
|
+
# @note This method must absolutely not block.
|
467
|
+
#
|
468
|
+
# @param token [String] a token sent by the server
|
469
|
+
# @return [nil]
|
470
|
+
end
|
245
471
|
end
|
246
472
|
end
|
247
473
|
|
248
474
|
require 'cql/client/connection_manager'
|
249
|
-
require 'cql/client/
|
475
|
+
require 'cql/client/connector'
|
250
476
|
require 'cql/client/null_logger'
|
251
477
|
require 'cql/client/column_metadata'
|
252
478
|
require 'cql/client/result_metadata'
|
253
|
-
require 'cql/client/query_result'
|
254
|
-
require 'cql/client/void_result'
|
255
479
|
require 'cql/client/query_trace'
|
256
480
|
require 'cql/client/execute_options_decoder'
|
257
481
|
require 'cql/client/keyspace_changer'
|
@@ -259,4 +483,9 @@ require 'cql/client/asynchronous_client'
|
|
259
483
|
require 'cql/client/asynchronous_prepared_statement'
|
260
484
|
require 'cql/client/synchronous_client'
|
261
485
|
require 'cql/client/synchronous_prepared_statement'
|
262
|
-
require 'cql/client/
|
486
|
+
require 'cql/client/batch'
|
487
|
+
require 'cql/client/query_result'
|
488
|
+
require 'cql/client/void_result'
|
489
|
+
require 'cql/client/request_runner'
|
490
|
+
require 'cql/client/authenticators'
|
491
|
+
require 'cql/client/peer_discovery'
|