cql-rb 1.0.0.pre0

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.
@@ -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