cql-rb 1.0.0.pre0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,40 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+
6
+ module Cql
7
+ describe Uuid do
8
+ describe '#initialize' do
9
+ it 'can be created from a string' do
10
+ Uuid.new('a4a70900-24e1-11df-8924-001ff3591711').to_s.should == 'a4a70900-24e1-11df-8924-001ff3591711'
11
+ end
12
+
13
+ it 'can be created from a number' do
14
+ Uuid.new(276263553384940695775376958868900023510).to_s.should == 'cfd66ccc-d857-4e90-b1e5-df98a3d40cd6'.force_encoding(::Encoding::ASCII)
15
+ end
16
+ end
17
+
18
+ describe '#eql?' do
19
+ it 'is equal to another Uuid with the same value' do
20
+ Uuid.new(276263553384940695775376958868900023510).should eql(Uuid.new('cfd66ccc-d857-4e90-b1e5-df98a3d40cd6'))
21
+ end
22
+
23
+ it 'aliases #== to #eql?' do
24
+ Uuid.new(276263553384940695775376958868900023510).should == Uuid.new('cfd66ccc-d857-4e90-b1e5-df98a3d40cd6')
25
+ end
26
+ end
27
+
28
+ describe '#to_s' do
29
+ it 'returns a UUID standard format' do
30
+ Uuid.new('a4a70900-24e1-11df-8924-001ff3591711').to_s.should == 'a4a70900-24e1-11df-8924-001ff3591711'
31
+ end
32
+ end
33
+
34
+ describe '#value' do
35
+ it 'returns the numeric value' do
36
+ Uuid.new('cfd66ccc-d857-4e90-b1e5-df98a3d40cd6').value.should == 276263553384940695775376958868900023510
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,101 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+
6
+ describe 'A CQL client' do
7
+ let :connection_options do
8
+ {:host => ENV['CASSANDRA_HOST']}
9
+ end
10
+
11
+ let :client do
12
+ Cql::Client.new(connection_options)
13
+ end
14
+
15
+ before do
16
+ client.start!
17
+ end
18
+
19
+ after do
20
+ client.shutdown!
21
+ end
22
+
23
+ it 'executes a query and returns the result' do
24
+ result = client.execute('SELECT * FROM system.schema_keyspaces')
25
+ result.should_not be_empty
26
+ end
27
+
28
+ it 'knows which keyspace it\'s in' do
29
+ client.use('system')
30
+ client.keyspace.should == 'system'
31
+ client.use('system_auth')
32
+ client.keyspace.should == 'system_auth'
33
+ end
34
+
35
+ it 'is not in a keyspace initially' do
36
+ client.keyspace.should be_nil
37
+ end
38
+
39
+ it 'can be initialized with a keyspace' do
40
+ c = Cql::Client.new(connection_options.merge(:keyspace => 'system'))
41
+ c.start!
42
+ begin
43
+ c.keyspace.should == 'system'
44
+ expect { c.execute('SELECT * FROM schema_keyspaces') }.to_not raise_error
45
+ ensure
46
+ c.shutdown!
47
+ end
48
+ end
49
+
50
+ it 'prepares a statement' do
51
+ statement = client.prepare('SELECT * FROM system.schema_keyspaces WHERE keyspace_name = ?')
52
+ statement.should_not be_nil
53
+ end
54
+
55
+ it 'executes a prepared statement' do
56
+ statement = client.prepare('SELECT * FROM system.schema_keyspaces WHERE keyspace_name = ?')
57
+ result = statement.execute('system')
58
+ result.should have(1).item
59
+ end
60
+
61
+ context 'with multiple connections' do
62
+ let :multi_client do
63
+ opts = connection_options.dup
64
+ opts[:host] = ([opts[:host]] * 10).join(',')
65
+ Cql::Client.new(opts)
66
+ end
67
+
68
+ before do
69
+ client.shutdown!
70
+ multi_client.start!
71
+ end
72
+
73
+ after do
74
+ multi_client.shutdown!
75
+ end
76
+
77
+ it 'handles keyspace changes with #use' do
78
+ multi_client.use('system')
79
+ 100.times do
80
+ result = multi_client.execute(%<SELECT * FROM schema_keyspaces WHERE keyspace_name = 'system'>)
81
+ result.should have(1).item
82
+ end
83
+ end
84
+
85
+ it 'handles keyspace changes with #execute' do
86
+ multi_client.execute('USE system')
87
+ 100.times do
88
+ result = multi_client.execute(%<SELECT * FROM schema_keyspaces WHERE keyspace_name = 'system'>)
89
+ result.should have(1).item
90
+ end
91
+ end
92
+
93
+ it 'executes a prepared statement' do
94
+ statement = multi_client.prepare('SELECT * FROM system.schema_keyspaces WHERE keyspace_name = ?')
95
+ 100.times do
96
+ result = statement.execute('system')
97
+ result.should have(1).item
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,326 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+
6
+ describe 'Protocol parsing and communication' do
7
+ let :io_reactor do
8
+ ir = Cql::Io::IoReactor.new
9
+ ir.start
10
+ ir.add_connection(ENV['CASSANDRA_HOST'], 9042).get
11
+ ir
12
+ end
13
+
14
+ let :keyspace_name do
15
+ "cql_rb_#{rand(1000)}"
16
+ end
17
+
18
+ after do
19
+ io_reactor.stop.get if io_reactor.running?
20
+ end
21
+
22
+ def execute_request(request)
23
+ io_reactor.queue_request(request).get.first
24
+ end
25
+
26
+ def query(cql, consistency=:one)
27
+ response = execute_request(Cql::Protocol::QueryRequest.new(cql, consistency))
28
+ raise response.to_s if response.is_a?(Cql::Protocol::ErrorResponse)
29
+ response
30
+ end
31
+
32
+ def create_keyspace!
33
+ query("CREATE KEYSPACE #{keyspace_name} WITH REPLICATION = {'CLASS': 'SimpleStrategy', 'replication_factor': 1}")
34
+ end
35
+
36
+ def use_keyspace!
37
+ query("USE #{keyspace_name}")
38
+ end
39
+
40
+ def drop_keyspace!
41
+ query("DROP KEYSPACE #{keyspace_name}")
42
+ end
43
+
44
+ def create_table!
45
+ query('CREATE TABLE users (user_name VARCHAR, password VARCHAR, email VARCHAR, PRIMARY KEY (user_name))')
46
+ end
47
+
48
+ def create_counters_table!
49
+ query('CREATE TABLE counters (id VARCHAR, c1 COUNTER, c2 COUNTER, PRIMARY KEY (id))')
50
+ end
51
+
52
+ def in_keyspace
53
+ create_keyspace!
54
+ use_keyspace!
55
+ begin
56
+ yield
57
+ ensure
58
+ begin
59
+ drop_keyspace!
60
+ rescue Errno::EPIPE => e
61
+ # ignore since we're shutting down
62
+ end
63
+ end
64
+ end
65
+
66
+ def in_keyspace_with_table
67
+ in_keyspace do
68
+ create_table!
69
+ yield
70
+ end
71
+ end
72
+
73
+ def in_keyspace_with_counters_table
74
+ in_keyspace do
75
+ create_counters_table!
76
+ yield
77
+ end
78
+ end
79
+
80
+ context 'when setting up' do
81
+ it 'sends OPTIONS and receives SUPPORTED' do
82
+ response = execute_request(Cql::Protocol::OptionsRequest.new)
83
+ response.options.should include('CQL_VERSION' => ['3.0.0'])
84
+ end
85
+
86
+ it 'sends STARTUP and receives READY' do
87
+ response = execute_request(Cql::Protocol::StartupRequest.new)
88
+ response.should be_a(Cql::Protocol::ReadyResponse)
89
+ end
90
+
91
+ it 'sends a bad STARTUP and receives ERROR' do
92
+ response = execute_request(Cql::Protocol::StartupRequest.new('9.9.9'))
93
+ response.code.should == 10
94
+ response.message.should include('not supported')
95
+ end
96
+ end
97
+
98
+ context 'when set up' do
99
+ before do
100
+ response = execute_request(Cql::Protocol::StartupRequest.new)
101
+ response
102
+ end
103
+
104
+ context 'with events' do
105
+ it 'sends a REGISTER request and receives READY' do
106
+ response = execute_request(Cql::Protocol::RegisterRequest.new('TOPOLOGY_CHANGE', 'STATUS_CHANGE', 'SCHEMA_CHANGE'))
107
+ response.should be_a(Cql::Protocol::ReadyResponse)
108
+ end
109
+
110
+ it 'passes events to listeners' do
111
+ semaphore = Queue.new
112
+ event = nil
113
+ execute_request(Cql::Protocol::RegisterRequest.new('SCHEMA_CHANGE'))
114
+ io_reactor.add_event_listener do |event_response|
115
+ event = event_response
116
+ semaphore << :ping
117
+ end
118
+ begin
119
+ create_keyspace!
120
+ semaphore.pop
121
+ event.change.should == 'CREATED'
122
+ event.keyspace.should == keyspace_name
123
+ ensure
124
+ drop_keyspace!
125
+ end
126
+ end
127
+ end
128
+
129
+ context 'when running queries' do
130
+ context 'with QUERY requests' do
131
+ it 'sends a USE command' do
132
+ response = query('USE system', :one)
133
+ response.keyspace.should == 'system'
134
+ end
135
+
136
+ it 'sends a bad CQL string and receives ERROR' do
137
+ response = execute_request(Cql::Protocol::QueryRequest.new('HELLO WORLD', :any))
138
+ response.should be_a(Cql::Protocol::ErrorResponse)
139
+ end
140
+
141
+ it 'sends a CREATE KEYSPACE command' do
142
+ response = query("CREATE KEYSPACE #{keyspace_name} WITH REPLICATION = {'CLASS': 'SimpleStrategy', 'replication_factor': 1}")
143
+ begin
144
+ response.change.should == 'CREATED'
145
+ response.keyspace.should == keyspace_name
146
+ ensure
147
+ drop_keyspace!
148
+ end
149
+ end
150
+
151
+ it 'sends a DROP KEYSPACE command' do
152
+ create_keyspace!
153
+ use_keyspace!
154
+ response = query("DROP KEYSPACE #{keyspace_name}")
155
+ response.change.should == 'DROPPED'
156
+ response.keyspace.should == keyspace_name
157
+ end
158
+
159
+ it 'sends an ALTER KEYSPACE command' do
160
+ create_keyspace!
161
+ begin
162
+ response = query("ALTER KEYSPACE #{keyspace_name} WITH DURABLE_WRITES = false")
163
+ response.change.should == 'UPDATED'
164
+ response.keyspace.should == keyspace_name
165
+ ensure
166
+ drop_keyspace!
167
+ end
168
+ end
169
+
170
+ it 'sends a CREATE TABLE command' do
171
+ in_keyspace do
172
+ response = query('CREATE TABLE users (user_name VARCHAR, password VARCHAR, email VARCHAR, PRIMARY KEY (user_name))')
173
+ response.change.should == 'CREATED'
174
+ response.keyspace.should == keyspace_name
175
+ response.table.should == 'users'
176
+ end
177
+ end
178
+
179
+ it 'sends a DROP TABLE command' do
180
+ in_keyspace_with_table do
181
+ response = query('DROP TABLE users')
182
+ response.change.should == 'DROPPED'
183
+ response.keyspace.should == keyspace_name
184
+ response.table.should == 'users'
185
+ end
186
+ end
187
+
188
+ it 'sends an ALTER TABLE command' do
189
+ in_keyspace_with_table do
190
+ response = query('ALTER TABLE users ADD age INT')
191
+ response.change.should == 'UPDATED'
192
+ response.keyspace.should == keyspace_name
193
+ response.table.should == 'users'
194
+ end
195
+ end
196
+
197
+ it 'sends an INSERT command' do
198
+ in_keyspace_with_table do
199
+ response = query(%<INSERT INTO users (user_name, email) VALUES ('phil', 'phil@heck.com')>)
200
+ response.should be_void
201
+ end
202
+ end
203
+
204
+ it 'sends an UPDATE command' do
205
+ in_keyspace_with_table do
206
+ query(%<INSERT INTO users (user_name, email) VALUES ('phil', 'phil@heck.com')>)
207
+ response = query(%<UPDATE users SET email = 'sue@heck.com' WHERE user_name = 'phil'>)
208
+ response.should be_void
209
+ end
210
+ end
211
+
212
+ it 'increments a counter' do
213
+ in_keyspace_with_counters_table do
214
+ response = query(%<UPDATE counters SET c1 = c1 + 1, c2 = c2 - 2 WHERE id = 'stuff'>)
215
+ response.should be_void
216
+ end
217
+ end
218
+
219
+ it 'sends a DELETE command' do
220
+ in_keyspace_with_table do
221
+ response = query(%<DELETE email FROM users WHERE user_name = 'sue'>)
222
+ response.should be_void
223
+ end
224
+ end
225
+
226
+ it 'sends a TRUNCATE command' do
227
+ in_keyspace_with_table do
228
+ response = query(%<TRUNCATE users>)
229
+ response.should be_void
230
+ end
231
+ end
232
+
233
+ it 'sends a BATCH command' do
234
+ in_keyspace_with_table do
235
+ response = query(<<-EOQ)
236
+ BEGIN BATCH
237
+ INSERT INTO users (user_name, email) VALUES ('phil', 'phil@heck.com')
238
+ INSERT INTO users (user_name, email) VALUES ('sue', 'sue@inter.net')
239
+ APPLY BATCH
240
+ EOQ
241
+ response.should be_void
242
+ end
243
+ end
244
+
245
+ it 'sends a SELECT command' do
246
+ in_keyspace_with_table do
247
+ query(%<INSERT INTO users (user_name, email) VALUES ('phil', 'phil@heck.com')>)
248
+ query(%<INSERT INTO users (user_name, email) VALUES ('sue', 'sue@inter.net')>)
249
+ response = query(%<SELECT * FROM users>, :quorum)
250
+ response.rows.should == [
251
+ {'user_name' => 'phil', 'email' => 'phil@heck.com', 'password' => nil},
252
+ {'user_name' => 'sue', 'email' => 'sue@inter.net', 'password' => nil}
253
+ ]
254
+ end
255
+ end
256
+ end
257
+
258
+ context 'with PREPARE requests' do
259
+ it 'sends a PREPARE request and receives RESULT' do
260
+ in_keyspace_with_table do
261
+ response = execute_request(Cql::Protocol::PrepareRequest.new('SELECT * FROM users WHERE user_name = ?'))
262
+ response.id.should_not be_nil
263
+ response.metadata.should_not be_nil
264
+ end
265
+ end
266
+
267
+ it 'sends an EXECUTE request and receives RESULT' do
268
+ in_keyspace do
269
+ create_table_cql = %<CREATE TABLE stuff (id1 UUID, id2 VARINT, id3 TIMESTAMP, value1 DOUBLE, value2 TIMEUUID, value3 BLOB, PRIMARY KEY (id1, id2, id3))>
270
+ insert_cql = %<INSERT INTO stuff (id1, id2, id3, value1, value2, value3) VALUES (?, ?, ?, ?, ?, ?)>
271
+ create_response = execute_request(Cql::Protocol::QueryRequest.new(create_table_cql, :one))
272
+ create_response.should_not be_a(Cql::Protocol::ErrorResponse)
273
+ prepare_response = execute_request(Cql::Protocol::PrepareRequest.new(insert_cql))
274
+ prepare_response.should_not be_a(Cql::Protocol::ErrorResponse)
275
+ execute_response = execute_request(Cql::Protocol::ExecuteRequest.new(prepare_response.id, prepare_response.metadata, [Cql::Uuid.new('cfd66ccc-d857-4e90-b1e5-df98a3d40cd6'), -12312312312, Time.now, 345345.234234, Cql::Uuid.new('a4a70900-24e1-11df-8924-001ff3591711'), "\xab\xcd\xef".force_encoding(::Encoding::BINARY)], :one))
276
+ execute_response.should_not be_a(Cql::Protocol::ErrorResponse)
277
+ end
278
+ end
279
+ end
280
+
281
+ context 'with pipelining' do
282
+ it 'handles multiple concurrent requests' do
283
+ in_keyspace_with_table do
284
+ futures = 10.times.map do
285
+ io_reactor.queue_request(Cql::Protocol::QueryRequest.new('SELECT * FROM users', :quorum))
286
+ end
287
+
288
+ futures << io_reactor.queue_request(Cql::Protocol::QueryRequest.new(%<INSERT INTO users (user_name, email) VALUES ('sam', 'sam@ham.com')>, :one))
289
+
290
+ Cql::Future.combine(*futures).get
291
+ end
292
+ end
293
+
294
+ it 'handles lots of concurrent requests' do
295
+ in_keyspace_with_table do
296
+ futures = 2000.times.map do
297
+ io_reactor.queue_request(Cql::Protocol::QueryRequest.new('SELECT * FROM users', :quorum))
298
+ end
299
+
300
+ Cql::Future.combine(*futures).get
301
+ end
302
+ end
303
+ end
304
+ end
305
+ end
306
+
307
+ context 'in special circumstances' do
308
+ it 'raises an exception when it cannot connect to Cassandra' do
309
+ io_reactor = Cql::Io::IoReactor.new(connection_timeout: 0.1)
310
+ io_reactor.start.get
311
+ expect { io_reactor.add_connection('example.com', 9042).get }.to raise_error(Cql::Io::ConnectionError)
312
+ expect { io_reactor.add_connection('blackhole', 9042).get }.to raise_error(Cql::Io::ConnectionError)
313
+ io_reactor.stop.get
314
+ end
315
+
316
+ it 'does nothing the second time #start is called' do
317
+ io_reactor = Cql::Io::IoReactor.new
318
+ io_reactor.start.get
319
+ io_reactor.add_connection('localhost', 9042)
320
+ io_reactor.queue_request(Cql::Protocol::StartupRequest.new).get
321
+ io_reactor.start.get
322
+ response = io_reactor.queue_request(Cql::Protocol::QueryRequest.new('USE system', :any)).get
323
+ response.should_not be_a(Cql::Protocol::ErrorResponse)
324
+ end
325
+ end
326
+ end