cql-rb 1.0.0.pre7 → 1.0.0.pre8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. data/lib/cql/byte_buffer.rb +22 -0
  2. data/lib/cql/client.rb +79 -310
  3. data/lib/cql/client/asynchronous_client.rb +254 -0
  4. data/lib/cql/client/asynchronous_prepared_statement.rb +19 -0
  5. data/lib/cql/client/column_metadata.rb +22 -0
  6. data/lib/cql/client/query_result.rb +34 -0
  7. data/lib/cql/client/result_metadata.rb +31 -0
  8. data/lib/cql/client/synchronous_client.rb +47 -0
  9. data/lib/cql/client/synchronous_prepared_statement.rb +47 -0
  10. data/lib/cql/future.rb +7 -3
  11. data/lib/cql/io.rb +1 -0
  12. data/lib/cql/io/io_reactor.rb +9 -3
  13. data/lib/cql/io/node_connection.rb +2 -2
  14. data/lib/cql/protocol.rb +5 -4
  15. data/lib/cql/protocol/request.rb +23 -0
  16. data/lib/cql/protocol/requests/credentials_request.rb +1 -1
  17. data/lib/cql/protocol/requests/execute_request.rb +13 -84
  18. data/lib/cql/protocol/requests/options_request.rb +1 -1
  19. data/lib/cql/protocol/requests/prepare_request.rb +2 -1
  20. data/lib/cql/protocol/requests/query_request.rb +3 -1
  21. data/lib/cql/protocol/requests/register_request.rb +1 -1
  22. data/lib/cql/protocol/requests/startup_request.rb +1 -2
  23. data/lib/cql/protocol/{response_body.rb → response.rb} +1 -1
  24. data/lib/cql/protocol/responses/authenticate_response.rb +1 -1
  25. data/lib/cql/protocol/responses/error_response.rb +1 -1
  26. data/lib/cql/protocol/responses/ready_response.rb +1 -1
  27. data/lib/cql/protocol/responses/result_response.rb +1 -1
  28. data/lib/cql/protocol/responses/rows_result_response.rb +1 -1
  29. data/lib/cql/protocol/responses/supported_response.rb +1 -1
  30. data/lib/cql/protocol/type_converter.rb +226 -46
  31. data/lib/cql/version.rb +1 -1
  32. data/spec/cql/byte_buffer_spec.rb +38 -0
  33. data/spec/cql/client/asynchronous_client_spec.rb +472 -0
  34. data/spec/cql/client/client_shared.rb +27 -0
  35. data/spec/cql/client/synchronous_client_spec.rb +104 -0
  36. data/spec/cql/client/synchronous_prepared_statement_spec.rb +65 -0
  37. data/spec/cql/future_spec.rb +4 -0
  38. data/spec/cql/io/io_reactor_spec.rb +39 -20
  39. data/spec/cql/protocol/request_spec.rb +17 -0
  40. data/spec/cql/protocol/requests/credentials_request_spec.rb +82 -0
  41. data/spec/cql/protocol/requests/execute_request_spec.rb +174 -0
  42. data/spec/cql/protocol/requests/options_request_spec.rb +24 -0
  43. data/spec/cql/protocol/requests/prepare_request_spec.rb +70 -0
  44. data/spec/cql/protocol/requests/query_request_spec.rb +95 -0
  45. data/spec/cql/protocol/requests/register_request_spec.rb +24 -0
  46. data/spec/cql/protocol/requests/startup_request_spec.rb +29 -0
  47. data/spec/integration/client_spec.rb +26 -19
  48. data/spec/integration/protocol_spec.rb +2 -2
  49. data/spec/integration/regression_spec.rb +1 -1
  50. metadata +35 -9
  51. data/lib/cql/protocol/request_body.rb +0 -15
  52. data/lib/cql/protocol/request_frame.rb +0 -20
  53. data/spec/cql/client_spec.rb +0 -454
  54. data/spec/cql/protocol/request_frame_spec.rb +0 -456
@@ -1,15 +0,0 @@
1
- # encoding: utf-8
2
-
3
- module Cql
4
- module Protocol
5
- class RequestBody
6
- include Encoding
7
-
8
- attr_reader :opcode
9
-
10
- def initialize(opcode)
11
- @opcode = opcode
12
- end
13
- end
14
- end
15
- end
@@ -1,20 +0,0 @@
1
- # encoding: utf-8
2
-
3
- module Cql
4
- module Protocol
5
- class RequestFrame
6
- def initialize(body, stream_id=0)
7
- @body = body
8
- @stream_id = stream_id
9
- raise InvalidStreamIdError, 'The stream ID must be between 0 and 127' unless 0 <= @stream_id && @stream_id < 128
10
- end
11
-
12
- def write(io)
13
- buffer = @body.write(ByteBuffer.new)
14
- io << [1, 0, @stream_id, @body.opcode].pack(Formats::HEADER_FORMAT)
15
- io << [buffer.length].pack(Formats::INT_FORMAT)
16
- io << buffer
17
- end
18
- end
19
- end
20
- end
@@ -1,454 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require 'spec_helper'
4
-
5
-
6
- module Cql
7
- describe Client do
8
- let :connection_options do
9
- {:host => 'example.com', :port => 12321, :io_reactor => io_reactor}
10
- end
11
-
12
- let :io_reactor do
13
- FakeIoReactor.new
14
- end
15
-
16
- let :client do
17
- described_class.new(connection_options)
18
- end
19
-
20
- def connections
21
- io_reactor.connections
22
- end
23
-
24
- def connection
25
- connections.first
26
- end
27
-
28
- def requests
29
- connection[:requests]
30
- end
31
-
32
- def last_request
33
- requests.last
34
- end
35
-
36
- describe '.connect' do
37
- it 'connects and returns the client' do
38
- client = described_class.connect(connection_options)
39
- client.should be_connected
40
- end
41
- end
42
-
43
- describe '#connect' do
44
- it 'connects' do
45
- client.connect
46
- connections.should have(1).item
47
- end
48
-
49
- it 'connects only once' do
50
- client.connect
51
- client.connect
52
- connections.should have(1).item
53
- end
54
-
55
- it 'connects to all hosts' do
56
- client.close
57
- io_reactor.stop.get
58
- io_reactor.start.get
59
-
60
- c = described_class.new(connection_options.merge(host: 'h1.example.com,h2.example.com,h3.example.com'))
61
- c.connect
62
- connections.should have(3).items
63
- end
64
-
65
- it 'returns itself' do
66
- client.connect.should equal(client)
67
- end
68
-
69
- it 'forwards the host and port' do
70
- client.connect
71
- connection[:host].should == 'example.com'
72
- connection[:port].should == 12321
73
- end
74
-
75
- it 'sends a startup request' do
76
- client.connect
77
- last_request.should be_a(Protocol::StartupRequest)
78
- end
79
-
80
- it 'sends a startup request to each connection' do
81
- client.close
82
- io_reactor.stop.get
83
- io_reactor.start.get
84
-
85
- c = described_class.new(connection_options.merge(host: 'h1.example.com,h2.example.com,h3.example.com'))
86
- c.connect
87
- connections.each do |cc|
88
- cc[:requests].last.should be_a(Protocol::StartupRequest)
89
- end
90
- end
91
-
92
- it 'is not in a keyspace' do
93
- client.connect
94
- client.keyspace.should be_nil
95
- end
96
-
97
- it 'changes to the keyspace given as an option' do
98
- c = described_class.new(connection_options.merge(:keyspace => 'hello_world'))
99
- c.connect
100
- last_request.should == Protocol::QueryRequest.new('USE hello_world', :one)
101
- end
102
-
103
- it 'validates the keyspace name before sending the USE command' do
104
- c = described_class.new(connection_options.merge(:keyspace => 'system; DROP KEYSPACE system'))
105
- expect { c.connect }.to raise_error(Client::InvalidKeyspaceNameError)
106
- requests.should_not include(Protocol::QueryRequest.new('USE system; DROP KEYSPACE system', :one))
107
- end
108
-
109
- it 're-raises any errors raised' do
110
- io_reactor.stub(:add_connection).and_raise(ArgumentError)
111
- expect { client.connect }.to raise_error(ArgumentError)
112
- end
113
-
114
- it 'is not connected if an error is raised' do
115
- io_reactor.stub(:add_connection).and_raise(ArgumentError)
116
- client.connect rescue nil
117
- client.should_not be_connected
118
- io_reactor.should_not be_running
119
- end
120
-
121
- it 'is connected after #connect returns' do
122
- client.connect
123
- client.should be_connected
124
- end
125
-
126
- context 'when the server requests authentication' do
127
- before do
128
- io_reactor.queue_response(Protocol::AuthenticateResponse.new('com.example.Auth'))
129
- end
130
-
131
- it 'sends credentials' do
132
- client = described_class.new(connection_options.merge(credentials: {'username' => 'foo', 'password' => 'bar'}))
133
- client.connect
134
- last_request.should == Protocol::CredentialsRequest.new('username' => 'foo', 'password' => 'bar')
135
- end
136
-
137
- it 'raises an error when no credentials have been given' do
138
- client = described_class.new(connection_options)
139
- expect { client.connect }.to raise_error(AuthenticationError)
140
- end
141
-
142
- it 'raises an error when the server responds with an error to the credentials request' do
143
- io_reactor.queue_response(Protocol::ErrorResponse.new(256, 'No way, José'))
144
- client = described_class.new(connection_options.merge(credentials: {'username' => 'foo', 'password' => 'bar'}))
145
- expect { client.connect }.to raise_error(AuthenticationError)
146
- end
147
-
148
- it 'shuts down the client when there is an authentication error' do
149
- io_reactor.queue_response(Protocol::ErrorResponse.new(256, 'No way, José'))
150
- client = described_class.new(connection_options.merge(credentials: {'username' => 'foo', 'password' => 'bar'}))
151
- client.connect rescue nil
152
- client.should_not be_connected
153
- io_reactor.should_not be_running
154
- end
155
- end
156
- end
157
-
158
- describe '#close' do
159
- it 'closes the connection' do
160
- client.connect
161
- client.close
162
- io_reactor.should_not be_running
163
- end
164
-
165
- it 'does nothing when called before #connect' do
166
- client.close
167
- end
168
-
169
- it 'accepts multiple calls to #close' do
170
- client.connect
171
- client.close
172
- client.close
173
- end
174
-
175
- it 'returns itself' do
176
- client.connect.close.should equal(client)
177
- end
178
- end
179
-
180
- describe '#use' do
181
- before do
182
- client.connect
183
- end
184
-
185
- it 'executes a USE query' do
186
- io_reactor.queue_response(Protocol::SetKeyspaceResultResponse.new('system'))
187
- client.use('system')
188
- last_request.should == Protocol::QueryRequest.new('USE system', :one)
189
- end
190
-
191
- it 'executes a USE query for each connection' do
192
- client.close
193
- io_reactor.stop.get
194
- io_reactor.start.get
195
-
196
- c = described_class.new(connection_options.merge(host: 'h1.example.com,h2.example.com,h3.example.com'))
197
- c.connect
198
-
199
- c.use('system')
200
- last_requests = connections.select { |c| c[:host] =~ /^h\d\.example\.com$/ }.sort_by { |c| c[:host] }.map { |c| c[:requests].last }
201
- last_requests.should == [
202
- Protocol::QueryRequest.new('USE system', :one),
203
- Protocol::QueryRequest.new('USE system', :one),
204
- Protocol::QueryRequest.new('USE system', :one)
205
- ]
206
- end
207
-
208
- it 'knows which keyspace it changed to' do
209
- io_reactor.queue_response(Protocol::SetKeyspaceResultResponse.new('system'))
210
- client.use('system')
211
- client.keyspace.should == 'system'
212
- end
213
-
214
- it 'raises an error if the keyspace name is not valid' do
215
- expect { client.use('system; DROP KEYSPACE system') }.to raise_error(Client::InvalidKeyspaceNameError)
216
- end
217
- end
218
-
219
- describe '#execute' do
220
- before do
221
- client.connect
222
- end
223
-
224
- it 'asks the connection to execute the query' do
225
- client.execute('UPDATE stuff SET thing = 1 WHERE id = 3')
226
- last_request.should == Protocol::QueryRequest.new('UPDATE stuff SET thing = 1 WHERE id = 3', :quorum)
227
- end
228
-
229
- it 'uses the specified consistency' do
230
- client.execute('UPDATE stuff SET thing = 1 WHERE id = 3', :three)
231
- last_request.should == Protocol::QueryRequest.new('UPDATE stuff SET thing = 1 WHERE id = 3', :three)
232
- end
233
-
234
- context 'with a void CQL query' do
235
- it 'returns nil' do
236
- io_reactor.queue_response(Protocol::VoidResultResponse.new)
237
- result = client.execute('UPDATE stuff SET thing = 1 WHERE id = 3')
238
- result.should be_nil
239
- end
240
- end
241
-
242
- context 'with a USE query' do
243
- it 'returns nil' do
244
- io_reactor.queue_response(Protocol::SetKeyspaceResultResponse.new('system'))
245
- result = client.execute('USE system')
246
- result.should be_nil
247
- end
248
-
249
- it 'knows which keyspace it changed to' do
250
- io_reactor.queue_response(Protocol::SetKeyspaceResultResponse.new('system'))
251
- client.execute('USE system')
252
- client.keyspace.should == 'system'
253
- end
254
-
255
- it 'detects that one connection changed to a keyspace and changes the others too' do
256
- client.close
257
- io_reactor.stop.get
258
- io_reactor.start.get
259
-
260
- c = described_class.new(connection_options.merge(host: 'h1.example.com,h2.example.com,h3.example.com'))
261
- c.connect
262
-
263
- io_reactor.queue_response(Protocol::SetKeyspaceResultResponse.new('system'), connections.find { |c| c[:host] == 'h1.example.com' }[:host])
264
- io_reactor.queue_response(Protocol::SetKeyspaceResultResponse.new('system'), connections.find { |c| c[:host] == 'h2.example.com' }[:host])
265
- io_reactor.queue_response(Protocol::SetKeyspaceResultResponse.new('system'), connections.find { |c| c[:host] == 'h3.example.com' }[:host])
266
-
267
- c.execute('USE system', :one)
268
- c.keyspace.should == 'system'
269
-
270
- last_requests = connections.select { |c| c[:host] =~ /^h\d\.example\.com$/ }.sort_by { |c| c[:host] }.map { |c| c[:requests].last }
271
- last_requests.should == [
272
- Protocol::QueryRequest.new('USE system', :one),
273
- Protocol::QueryRequest.new('USE system', :one),
274
- Protocol::QueryRequest.new('USE system', :one)
275
- ]
276
- end
277
- end
278
-
279
- context 'with an SELECT query' do
280
- let :rows do
281
- [['xyz', 'abc'], ['abc', 'xyz'], ['123', 'xyz']]
282
- end
283
-
284
- let :metadata do
285
- [['thingies', 'things', 'thing', :text], ['thingies', 'things', 'item', :text]]
286
- end
287
-
288
- let :result do
289
- io_reactor.queue_response(Protocol::RowsResultResponse.new(rows, metadata))
290
- client.execute('SELECT * FROM things')
291
- end
292
-
293
- it 'returns an Enumerable of rows' do
294
- row_count = 0
295
- result.each do |row|
296
- row_count += 1
297
- end
298
- row_count.should == 3
299
- end
300
-
301
- context 'with metadata that' do
302
- it 'has keyspace, table and type information' do
303
- result.metadata['item'].keyspace.should == 'thingies'
304
- result.metadata['item'].table.should == 'things'
305
- result.metadata['item'].column_name.should == 'item'
306
- result.metadata['item'].type.should == :text
307
- end
308
-
309
- it 'is an Enumerable' do
310
- result.metadata.map(&:type).should == [:text, :text]
311
- end
312
-
313
- it 'is splattable' do
314
- ks, table, col, type = result.metadata['thing']
315
- ks.should == 'thingies'
316
- table.should == 'things'
317
- col.should == 'thing'
318
- type.should == :text
319
- end
320
- end
321
- end
322
-
323
- context 'when the response is an error' do
324
- it 'raises an error' do
325
- io_reactor.queue_response(Protocol::ErrorResponse.new(0xabcd, 'Blurgh'))
326
- expect { client.execute('SELECT * FROM things') }.to raise_error(QueryError, 'Blurgh')
327
- end
328
- end
329
- end
330
-
331
- describe '#prepare' do
332
- let :id do
333
- 'A' * 32
334
- end
335
-
336
- let :metadata do
337
- [['stuff', 'things', 'item', :varchar]]
338
- end
339
-
340
- before do
341
- client.connect
342
- end
343
-
344
- it 'sends a prepare request' do
345
- client.prepare('SELECT * FROM system.peers')
346
- last_request.should == Protocol::PrepareRequest.new('SELECT * FROM system.peers')
347
- end
348
-
349
- it 'returns a prepared statement' do
350
- io_reactor.queue_response(Protocol::PreparedResultResponse.new('A' * 32, [['stuff', 'things', 'item', :varchar]]))
351
- statement = client.prepare('SELECT * FROM stuff.things WHERE item = ?')
352
- statement.should_not be_nil
353
- end
354
-
355
- it 'executes a prepared statement' do
356
- io_reactor.queue_response(Protocol::PreparedResultResponse.new(id, metadata))
357
- statement = client.prepare('SELECT * FROM stuff.things WHERE item = ?')
358
- statement.execute('foo')
359
- last_request.should == Protocol::ExecuteRequest.new(id, metadata, ['foo'], :quorum)
360
- end
361
-
362
- it 'returns a prepared statement that knows the metadata' do
363
- io_reactor.queue_response(Protocol::PreparedResultResponse.new(id, metadata))
364
- statement = client.prepare('SELECT * FROM stuff.things WHERE item = ?')
365
- statement.metadata['item'].type == :varchar
366
- end
367
-
368
- it 'executes a prepared statement with a specific consistency level' do
369
- io_reactor.queue_response(Protocol::PreparedResultResponse.new(id, metadata))
370
- statement = client.prepare('SELECT * FROM stuff.things WHERE item = ?')
371
- statement.execute('thing', :local_quorum)
372
- last_request.should == Protocol::ExecuteRequest.new(id, metadata, ['thing'], :local_quorum)
373
- end
374
-
375
- it 'executes a prepared statement using the right connection' do
376
- client.close
377
- io_reactor.stop.get
378
- io_reactor.start.get
379
-
380
- c = described_class.new(connection_options.merge(host: 'h1.example.com,h2.example.com,h3.example.com'))
381
- c.connect
382
-
383
- io_reactor.queue_response(Protocol::PreparedResultResponse.new('A' * 32, metadata))
384
- io_reactor.queue_response(Protocol::PreparedResultResponse.new('B' * 32, metadata))
385
- io_reactor.queue_response(Protocol::PreparedResultResponse.new('C' * 32, metadata))
386
-
387
- statement1 = c.prepare('SELECT * FROM stuff.things WHERE item = ?')
388
- statement1_connection = io_reactor.last_used_connection
389
- statement2 = c.prepare('SELECT * FROM stuff.things WHERE item = ?')
390
- statement2_connection = io_reactor.last_used_connection
391
- statement3 = c.prepare('SELECT * FROM stuff.things WHERE item = ?')
392
- statement3_connection = io_reactor.last_used_connection
393
-
394
- io_reactor.queue_response(Protocol::RowsResultResponse.new([{'thing' => 'foo1'}], metadata), statement1_connection[:host])
395
- io_reactor.queue_response(Protocol::RowsResultResponse.new([{'thing' => 'foo2'}], metadata), statement2_connection[:host])
396
- io_reactor.queue_response(Protocol::RowsResultResponse.new([{'thing' => 'foo3'}], metadata), statement3_connection[:host])
397
-
398
- statement1.execute('foo').first.should == {'thing' => 'foo1'}
399
- statement2.execute('foo').first.should == {'thing' => 'foo2'}
400
- statement3.execute('foo').first.should == {'thing' => 'foo3'}
401
- end
402
- end
403
-
404
- context 'when not connected' do
405
- it 'is not connected before #connect has been called' do
406
- client.should_not be_connected
407
- end
408
-
409
- it 'is not connected after #close has been called' do
410
- client.connect
411
- client.close
412
- client.should_not be_connected
413
- end
414
-
415
- it 'complains when #use is called before #connect' do
416
- expect { client.use('system') }.to raise_error(Client::NotConnectedError)
417
- end
418
-
419
- it 'complains when #use is called after #close' do
420
- client.connect
421
- client.close
422
- expect { client.use('system') }.to raise_error(Client::NotConnectedError)
423
- end
424
-
425
- it 'complains when #execute is called before #connect' do
426
- expect { client.execute('DELETE FROM stuff WHERE id = 3') }.to raise_error(Client::NotConnectedError)
427
- end
428
-
429
- it 'complains when #execute is called after #close' do
430
- client.connect
431
- client.close
432
- expect { client.execute('DELETE FROM stuff WHERE id = 3') }.to raise_error(Client::NotConnectedError)
433
- end
434
-
435
- it 'complains when #prepare is called before #connect' do
436
- expect { client.prepare('DELETE FROM stuff WHERE id = 3') }.to raise_error(Client::NotConnectedError)
437
- end
438
-
439
- it 'complains when #prepare is called after #close' do
440
- client.connect
441
- client.close
442
- expect { client.prepare('DELETE FROM stuff WHERE id = 3') }.to raise_error(Client::NotConnectedError)
443
- end
444
-
445
- it 'complains when #execute of a prepared statement is called after #close' do
446
- client.connect
447
- io_reactor.queue_response(Protocol::PreparedResultResponse.new('A' * 32, []))
448
- statement = client.prepare('DELETE FROM stuff WHERE id = 3')
449
- client.close
450
- expect { statement.execute }.to raise_error(Client::NotConnectedError)
451
- end
452
- end
453
- end
454
- end