cql-rb 1.0.0.pre7 → 1.0.0.pre8

Sign up to get free protection for your applications and to get access to all the features.
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