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.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +4 -0
  3. data/README.md +139 -17
  4. data/lib/cql/client.rb +237 -8
  5. data/lib/cql/client/asynchronous_client.rb +138 -54
  6. data/lib/cql/client/asynchronous_prepared_statement.rb +41 -6
  7. data/lib/cql/client/authenticators.rb +46 -0
  8. data/lib/cql/client/batch.rb +115 -0
  9. data/lib/cql/client/connector.rb +255 -0
  10. data/lib/cql/client/execute_options_decoder.rb +25 -9
  11. data/lib/cql/client/keyspace_changer.rb +5 -5
  12. data/lib/cql/client/peer_discovery.rb +33 -0
  13. data/lib/cql/client/query_result.rb +124 -1
  14. data/lib/cql/client/request_runner.rb +4 -2
  15. data/lib/cql/client/synchronous_client.rb +14 -2
  16. data/lib/cql/client/synchronous_prepared_statement.rb +19 -1
  17. data/lib/cql/future.rb +97 -50
  18. data/lib/cql/io/connection.rb +0 -1
  19. data/lib/cql/io/io_reactor.rb +1 -1
  20. data/lib/cql/protocol.rb +8 -1
  21. data/lib/cql/protocol/cql_protocol_handler.rb +2 -2
  22. data/lib/cql/protocol/decoding.rb +10 -15
  23. data/lib/cql/protocol/frame_decoder.rb +2 -1
  24. data/lib/cql/protocol/frame_encoder.rb +5 -4
  25. data/lib/cql/protocol/requests/auth_response_request.rb +31 -0
  26. data/lib/cql/protocol/requests/batch_request.rb +59 -0
  27. data/lib/cql/protocol/requests/credentials_request.rb +1 -1
  28. data/lib/cql/protocol/requests/execute_request.rb +45 -17
  29. data/lib/cql/protocol/requests/options_request.rb +1 -1
  30. data/lib/cql/protocol/requests/prepare_request.rb +1 -1
  31. data/lib/cql/protocol/requests/query_request.rb +97 -5
  32. data/lib/cql/protocol/requests/register_request.rb +1 -1
  33. data/lib/cql/protocol/requests/startup_request.rb +4 -4
  34. data/lib/cql/protocol/response.rb +2 -2
  35. data/lib/cql/protocol/responses/auth_challenge_response.rb +25 -0
  36. data/lib/cql/protocol/responses/auth_success_response.rb +25 -0
  37. data/lib/cql/protocol/responses/authenticate_response.rb +1 -1
  38. data/lib/cql/protocol/responses/detailed_error_response.rb +1 -1
  39. data/lib/cql/protocol/responses/error_response.rb +3 -2
  40. data/lib/cql/protocol/responses/event_response.rb +3 -2
  41. data/lib/cql/protocol/responses/prepared_result_response.rb +10 -6
  42. data/lib/cql/protocol/responses/raw_rows_result_response.rb +27 -0
  43. data/lib/cql/protocol/responses/ready_response.rb +1 -1
  44. data/lib/cql/protocol/responses/result_response.rb +2 -2
  45. data/lib/cql/protocol/responses/rows_result_response.rb +43 -23
  46. data/lib/cql/protocol/responses/schema_change_event_response.rb +1 -1
  47. data/lib/cql/protocol/responses/schema_change_result_response.rb +1 -1
  48. data/lib/cql/protocol/responses/set_keyspace_result_response.rb +1 -1
  49. data/lib/cql/protocol/responses/status_change_event_response.rb +1 -1
  50. data/lib/cql/protocol/responses/supported_response.rb +1 -1
  51. data/lib/cql/protocol/responses/void_result_response.rb +1 -1
  52. data/lib/cql/protocol/type_converter.rb +2 -2
  53. data/lib/cql/uuid.rb +2 -2
  54. data/lib/cql/version.rb +1 -1
  55. data/spec/cql/client/asynchronous_client_spec.rb +493 -50
  56. data/spec/cql/client/asynchronous_prepared_statement_spec.rb +193 -11
  57. data/spec/cql/client/authenticators_spec.rb +56 -0
  58. data/spec/cql/client/batch_spec.rb +277 -0
  59. data/spec/cql/client/connector_spec.rb +606 -0
  60. data/spec/cql/client/execute_options_decoder_spec.rb +95 -0
  61. data/spec/cql/client/keyspace_changer_spec.rb +8 -8
  62. data/spec/cql/client/peer_discovery_spec.rb +92 -0
  63. data/spec/cql/client/query_result_spec.rb +352 -0
  64. data/spec/cql/client/request_runner_spec.rb +31 -5
  65. data/spec/cql/client/synchronous_client_spec.rb +44 -1
  66. data/spec/cql/client/synchronous_prepared_statement_spec.rb +63 -1
  67. data/spec/cql/future_spec.rb +50 -2
  68. data/spec/cql/protocol/cql_protocol_handler_spec.rb +16 -5
  69. data/spec/cql/protocol/decoding_spec.rb +16 -6
  70. data/spec/cql/protocol/encoding_spec.rb +3 -1
  71. data/spec/cql/protocol/frame_encoder_spec.rb +99 -50
  72. data/spec/cql/protocol/requests/auth_response_request_spec.rb +62 -0
  73. data/spec/cql/protocol/requests/batch_request_spec.rb +155 -0
  74. data/spec/cql/protocol/requests/credentials_request_spec.rb +1 -1
  75. data/spec/cql/protocol/requests/execute_request_spec.rb +184 -71
  76. data/spec/cql/protocol/requests/options_request_spec.rb +1 -1
  77. data/spec/cql/protocol/requests/prepare_request_spec.rb +1 -1
  78. data/spec/cql/protocol/requests/query_request_spec.rb +255 -32
  79. data/spec/cql/protocol/requests/register_request_spec.rb +1 -1
  80. data/spec/cql/protocol/requests/startup_request_spec.rb +12 -6
  81. data/spec/cql/protocol/responses/auth_challenge_response_spec.rb +31 -0
  82. data/spec/cql/protocol/responses/auth_success_response_spec.rb +31 -0
  83. data/spec/cql/protocol/responses/authenticate_response_spec.rb +2 -1
  84. data/spec/cql/protocol/responses/detailed_error_response_spec.rb +14 -7
  85. data/spec/cql/protocol/responses/error_response_spec.rb +4 -2
  86. data/spec/cql/protocol/responses/event_response_spec.rb +7 -4
  87. data/spec/cql/protocol/responses/prepared_result_response_spec.rb +89 -34
  88. data/spec/cql/protocol/responses/raw_rows_result_response_spec.rb +66 -0
  89. data/spec/cql/protocol/responses/ready_response_spec.rb +1 -1
  90. data/spec/cql/protocol/responses/result_response_spec.rb +19 -7
  91. data/spec/cql/protocol/responses/rows_result_response_spec.rb +56 -11
  92. data/spec/cql/protocol/responses/schema_change_event_response_spec.rb +2 -1
  93. data/spec/cql/protocol/responses/schema_change_result_response_spec.rb +2 -1
  94. data/spec/cql/protocol/responses/set_keyspace_result_response_spec.rb +1 -1
  95. data/spec/cql/protocol/responses/status_change_event_response_spec.rb +2 -1
  96. data/spec/cql/protocol/responses/supported_response_spec.rb +2 -1
  97. data/spec/cql/protocol/responses/topology_change_event_response_spec.rb +2 -1
  98. data/spec/cql/protocol/responses/void_result_response_spec.rb +1 -1
  99. data/spec/cql/protocol/type_converter_spec.rb +21 -4
  100. data/spec/cql/uuid_spec.rb +10 -3
  101. data/spec/integration/client_spec.rb +251 -28
  102. data/spec/integration/protocol_spec.rb +213 -62
  103. data/spec/integration/regression_spec.rb +4 -1
  104. data/spec/integration/uuid_spec.rb +4 -1
  105. data/spec/support/fake_io_reactor.rb +5 -5
  106. metadata +36 -7
  107. data/lib/cql/client/connection_helper.rb +0 -181
  108. 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: 6dd6370f6131da7cfdd42516a511d5bcc0edbc6a
4
- data.tar.gz: fc06b032f6f7c1219b8ed6f05c6421b97b76a9ee
3
+ metadata.gz: 1b00538d250d975f9b1ed2795bf947d9baf97c35
4
+ data.tar.gz: e1b27e7d7665bb8545b14c088fb7076ab5e76b01
5
5
  SHA512:
6
- metadata.gz: cc7feeb45c85c6dae447196786859fcd30e045a03c200fa7dea908bcdc923ca456de9ea320693f68d60cbd4eba0daf077f67098d095225d04116695e1cc0b889
7
- data.tar.gz: f2a77f2ea81b1b910fb7064e373c1cca1a4658184369d85d2d589f27a03ed12d2303abe7b9ed33f394d774d1df3394c62d3ed356e8a0f97ed5846c7b3bacc702
6
+ metadata.gz: 9b24cfa7ecbd973138d21c64e6f64ab02673deefc8f9bab46e0cc1658d041bf15b237326aaec3cfd6f83f8152a802d4efed71cef505fc3fe6102d3d87c7fd49e
7
+ data.tar.gz: b3a5f99e920989e64ebde8669c3339890ee6b8bdd0c882e65ea2354d0055bd29bd48f2089355d13c279b64379fbb258cf0987306b6e9b11c49caec51d86adc54
@@ -0,0 +1,4 @@
1
+ --no-private
2
+ --markup markdown
3
+ lib/**/*.rb
4
+ -- README
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
- _Please note that this is the readme for the development version and that some features described here might not yet have been released._
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 in Travis with Ruby 1.9.3, 2.0, JRuby 1.7 and Rubinius 2.0.
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
- ## Configure Cassandra
17
+ if you want to use compression you should also install the [snappy gem](http://rubygems.org/gems/snappy):
17
18
 
18
- 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`.
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](http://rubydoc.info/gems/cql-rb/frames) is available from [rubydoc.info](http://rubydoc.info/).
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](https://github.com/apache/cassandra/blob/cassandra-1.2/doc/cql3/CQL.textile).
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](https://github.com/apache/cassandra/blob/cassandra-1.2/doc/cql3/CQL.textile) and the [Cassandra query documentation](http://www.datastax.com/docs/1.2/cql_cli/querying_cql).
293
+ Read more about CQL3 in the [CQL3 syntax documentation][2] and the [Cassandra query documentation][3].
187
294
 
188
- # Cassandra 2.0
295
+ # Troubleshooting
189
296
 
190
- Cassandra 2.0 introduced a new version of the native protocol with some new features like argument interpolation in non-prepared statements, result set cursors, a new authentication mechanism and the `SERIAL` consistency. These features are not yet supported, but the driver will work with Cassandra 2.0 using the earlier protocol. Support for all of the features of the new protocol is being worked on. If there is a particular feature that you would want to see implemented, open an issue and describe your use case. This helps with prioritizing what should be implemented first.
297
+ ## I get "connection refused" errors
191
298
 
192
- # Troubleshooting
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 and authentication it is unfortunately not supported yet. DataStax backported the authentication from Cassandra 2.0 into DSE, even though it only uses Cassandra 1.2. The authentication mechanism in Cassandra 2.0 is very different and you will have to wait until support for Cassandra 2.0 is added to cql-rb before it will work with DSE.
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. Batches also mean that in most cases you need to smash strings together to create a big CQL string, so you increase the size of your requests, using up more bandwidth and making the server have to do more CQL parsing. Prepared statements are almost always a better choice for performance.
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, when support for that arrives in cql-rb, this advice should be reconsidered.
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](http://rubydoc.info/gems/cql-rb/frames) 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.
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
@@ -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=nil)
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] :timeout (nil) How long to wait
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 [Symbol] :trace (false) Request tracing
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/connection_helper'
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/request_runner'
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'