cql-rb 1.1.0.pre3 → 1.1.0.pre6
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +2 -2
- data/lib/cql/client.rb +9 -5
- data/lib/cql/client/asynchronous_client.rb +105 -192
- data/lib/cql/client/asynchronous_prepared_statement.rb +51 -9
- data/lib/cql/client/connection_helper.rb +155 -0
- data/lib/cql/client/connection_manager.rb +56 -0
- data/lib/cql/client/keyspace_changer.rb +27 -0
- data/lib/cql/client/null_logger.rb +21 -0
- data/lib/cql/client/request_runner.rb +5 -3
- data/lib/cql/client/synchronous_client.rb +5 -5
- data/lib/cql/client/synchronous_prepared_statement.rb +4 -8
- data/lib/cql/future.rb +320 -210
- data/lib/cql/io/connection.rb +5 -5
- data/lib/cql/io/io_reactor.rb +21 -23
- data/lib/cql/protocol/cql_protocol_handler.rb +69 -38
- data/lib/cql/protocol/encoding.rb +5 -1
- data/lib/cql/protocol/requests/register_request.rb +2 -0
- data/lib/cql/protocol/type_converter.rb +1 -0
- data/lib/cql/version.rb +1 -1
- data/spec/cql/client/asynchronous_client_spec.rb +368 -175
- data/spec/cql/client/asynchronous_prepared_statement_spec.rb +132 -22
- data/spec/cql/client/connection_helper_spec.rb +335 -0
- data/spec/cql/client/connection_manager_spec.rb +118 -0
- data/spec/cql/client/keyspace_changer_spec.rb +50 -0
- data/spec/cql/client/request_runner_spec.rb +12 -12
- data/spec/cql/client/synchronous_client_spec.rb +15 -15
- data/spec/cql/client/synchronous_prepared_statement_spec.rb +15 -11
- data/spec/cql/future_spec.rb +529 -301
- data/spec/cql/io/connection_spec.rb +12 -12
- data/spec/cql/io/io_reactor_spec.rb +61 -61
- data/spec/cql/protocol/cql_protocol_handler_spec.rb +26 -12
- data/spec/cql/protocol/encoding_spec.rb +5 -0
- data/spec/cql/protocol/type_converter_spec.rb +1 -1
- data/spec/cql/time_uuid_spec.rb +7 -7
- data/spec/integration/client_spec.rb +2 -2
- data/spec/integration/io_spec.rb +20 -20
- data/spec/integration/protocol_spec.rb +17 -17
- data/spec/integration/regression_spec.rb +6 -0
- data/spec/integration/uuid_spec.rb +4 -0
- data/spec/support/fake_io_reactor.rb +38 -8
- data/spec/support/fake_server.rb +3 -3
- metadata +12 -2
data/README.md
CHANGED
@@ -118,7 +118,7 @@ A prepared statement can be run many times, but the CQL parsing will only be don
|
|
118
118
|
|
119
119
|
At this time prepared statements are local to a single connection. Even if you connect to multiple nodes a prepared statement is only ever going to be executed against one of the nodes.
|
120
120
|
|
121
|
-
# Consistency
|
121
|
+
# Consistency
|
122
122
|
|
123
123
|
The `#execute` (of `Client` and `PreparedStatement`) method supports setting the desired consistency level for the statement:
|
124
124
|
|
@@ -139,7 +139,7 @@ The possible values are:
|
|
139
139
|
|
140
140
|
The default consistency level is `:quorum`.
|
141
141
|
|
142
|
-
Consistency
|
142
|
+
Consistency is ignored for `USE`, `TRUNCATE`, `CREATE` and `ALTER` statements, and some (like `:any`) aren't allowed in all situations.
|
143
143
|
|
144
144
|
## CQL3
|
145
145
|
|
data/lib/cql/client.rb
CHANGED
@@ -147,13 +147,13 @@ module Cql
|
|
147
147
|
# Execute the prepared statement with a list of values for the bound parameters.
|
148
148
|
#
|
149
149
|
# The number of arguments must equal the number of bound parameters.
|
150
|
-
# To set the consistency
|
151
|
-
#
|
152
|
-
#
|
153
|
-
#
|
150
|
+
# To set the consistency for the request you pass a consistency (as a
|
151
|
+
# symbol) as the last argument. Needless to say, if you pass the value for
|
152
|
+
# one bound parameter too few, and then a consistency, or if you pass too
|
153
|
+
# many values, you will get weird errors.
|
154
154
|
#
|
155
155
|
# @param args [Array] the values for the bound parameters, and optionally
|
156
|
-
# the desired consistency
|
156
|
+
# the desired consistency, as a symbol (defaults to :quorum)
|
157
157
|
# @raise [Cql::NotConnectedError] raised when the client is not connected
|
158
158
|
# @raise [Cql::QueryError] raised when there is an error on the server side
|
159
159
|
# @return [nil, Cql::Client::QueryResult] Most statements have no result and return
|
@@ -165,9 +165,13 @@ module Cql
|
|
165
165
|
end
|
166
166
|
end
|
167
167
|
|
168
|
+
require 'cql/client/connection_manager'
|
169
|
+
require 'cql/client/connection_helper'
|
170
|
+
require 'cql/client/null_logger'
|
168
171
|
require 'cql/client/column_metadata'
|
169
172
|
require 'cql/client/result_metadata'
|
170
173
|
require 'cql/client/query_result'
|
174
|
+
require 'cql/client/keyspace_changer'
|
171
175
|
require 'cql/client/asynchronous_client'
|
172
176
|
require 'cql/client/asynchronous_prepared_statement'
|
173
177
|
require 'cql/client/synchronous_client'
|
@@ -5,41 +5,45 @@ module Cql
|
|
5
5
|
# @private
|
6
6
|
class AsynchronousClient < Client
|
7
7
|
def initialize(options={})
|
8
|
-
@
|
9
|
-
@hosts = extract_hosts(options)
|
10
|
-
@port = options[:port] || 9042
|
8
|
+
@logger = options[:logger] || NullLogger.new
|
11
9
|
@io_reactor = options[:io_reactor] || Io::IoReactor.new(Protocol::CqlProtocolHandler)
|
10
|
+
@hosts = extract_hosts(options)
|
11
|
+
@initial_keyspace = options[:keyspace]
|
12
|
+
@default_consistency = options[:default_consistency] || DEFAULT_CONSISTENCY
|
12
13
|
@lock = Mutex.new
|
13
14
|
@connected = false
|
14
15
|
@connecting = false
|
15
16
|
@closing = false
|
16
|
-
@initial_keyspace = options[:keyspace]
|
17
|
-
@credentials = options[:credentials]
|
18
17
|
@request_runner = RequestRunner.new
|
18
|
+
@keyspace_changer = KeyspaceChanger.new
|
19
|
+
@connection_manager = ConnectionManager.new
|
20
|
+
port = options[:port] || DEFAULT_PORT
|
21
|
+
credentials = options[:credentials]
|
22
|
+
connection_timeout = options[:connection_timeout] || DEFAULT_CONNECTION_TIMEOUT
|
23
|
+
@connection_helper = ConnectionHelper.new(@io_reactor, port, credentials, connection_timeout, @logger)
|
19
24
|
end
|
20
25
|
|
21
26
|
def connect
|
22
27
|
@lock.synchronize do
|
23
28
|
return @connected_future if can_execute?
|
24
29
|
@connecting = true
|
25
|
-
@connected_future =
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
@connecting = false
|
40
|
-
@connected = false
|
30
|
+
@connected_future = begin
|
31
|
+
f = @connection_helper.connect(@hosts, @initial_keyspace)
|
32
|
+
if @closing
|
33
|
+
ff = @closed_future
|
34
|
+
ff = ff.flat_map { f }
|
35
|
+
ff = ff.fallback { f }
|
36
|
+
else
|
37
|
+
ff = f
|
38
|
+
end
|
39
|
+
ff.on_value do |connections|
|
40
|
+
@connection_manager.add_connections(connections)
|
41
|
+
register_event_listener(@connection_manager.random_connection)
|
42
|
+
end
|
43
|
+
ff.map { self }
|
41
44
|
end
|
42
45
|
end
|
46
|
+
@connected_future.on_complete(&method(:connected))
|
43
47
|
@connected_future
|
44
48
|
end
|
45
49
|
|
@@ -47,25 +51,19 @@ module Cql
|
|
47
51
|
@lock.synchronize do
|
48
52
|
return @closed_future if @closing
|
49
53
|
@closing = true
|
50
|
-
@closed_future =
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
@connected = false
|
61
|
-
end
|
62
|
-
end
|
63
|
-
@closed_future.on_failure do
|
64
|
-
@lock.synchronize do
|
65
|
-
@closing = false
|
66
|
-
@connected = false
|
54
|
+
@closed_future = begin
|
55
|
+
f = @io_reactor.stop
|
56
|
+
if @connecting
|
57
|
+
ff = @connected_future
|
58
|
+
ff = f.flat_map { ff }
|
59
|
+
ff = f.fallback { ff }
|
60
|
+
else
|
61
|
+
ff = f
|
62
|
+
end
|
63
|
+
ff.map { self }
|
67
64
|
end
|
68
65
|
end
|
66
|
+
@closed_future.on_complete(&method(:closed))
|
69
67
|
@closed_future
|
70
68
|
end
|
71
69
|
|
@@ -74,55 +72,38 @@ module Cql
|
|
74
72
|
end
|
75
73
|
|
76
74
|
def keyspace
|
77
|
-
@
|
78
|
-
@connections.first.keyspace
|
79
|
-
end
|
75
|
+
@connection_manager.random_connection.keyspace
|
80
76
|
end
|
81
77
|
|
82
78
|
def use(keyspace)
|
83
79
|
with_failure_handler do
|
84
|
-
connections = @
|
85
|
-
@connections.select { |c| c.keyspace != keyspace }
|
86
|
-
end
|
80
|
+
connections = @connection_manager.select { |c| c.keyspace != keyspace }
|
87
81
|
if connections.any?
|
88
|
-
futures = connections.map { |connection| use_keyspace(keyspace, connection) }
|
89
|
-
Future.
|
82
|
+
futures = connections.map { |connection| @keyspace_changer.use_keyspace(keyspace, connection) }
|
83
|
+
Future.all(*futures).map { nil }
|
90
84
|
else
|
91
|
-
Future.
|
85
|
+
Future.resolved
|
92
86
|
end
|
93
87
|
end
|
94
88
|
end
|
95
89
|
|
96
90
|
def execute(cql, consistency=nil)
|
97
91
|
with_failure_handler do
|
98
|
-
consistency
|
99
|
-
execute_request(Protocol::QueryRequest.new(cql, consistency))
|
92
|
+
execute_request(Protocol::QueryRequest.new(cql, consistency || @default_consistency))
|
100
93
|
end
|
101
94
|
end
|
102
95
|
|
103
96
|
def prepare(cql)
|
104
97
|
with_failure_handler do
|
105
|
-
|
98
|
+
AsynchronousPreparedStatement.prepare(cql, @default_consistency, @connection_manager, @logger)
|
106
99
|
end
|
107
100
|
end
|
108
101
|
|
109
102
|
private
|
110
103
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
class FailedConnection
|
116
|
-
attr_reader :error
|
117
|
-
|
118
|
-
def initialize(error)
|
119
|
-
@error = error
|
120
|
-
end
|
121
|
-
|
122
|
-
def connected?
|
123
|
-
false
|
124
|
-
end
|
125
|
-
end
|
104
|
+
DEFAULT_CONSISTENCY = :quorum
|
105
|
+
DEFAULT_PORT = 9042
|
106
|
+
DEFAULT_CONNECTION_TIMEOUT = 10
|
126
107
|
|
127
108
|
def extract_hosts(options)
|
128
109
|
if options[:hosts]
|
@@ -134,159 +115,91 @@ module Cql
|
|
134
115
|
end
|
135
116
|
end
|
136
117
|
|
137
|
-
def
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
name =~ KEYSPACE_NAME_PATTERN
|
143
|
-
end
|
144
|
-
|
145
|
-
def with_failure_handler
|
146
|
-
return Future.failed(NotConnectedError.new) unless can_execute?
|
147
|
-
yield
|
148
|
-
rescue => e
|
149
|
-
Future.failed(e)
|
150
|
-
end
|
151
|
-
|
152
|
-
def when_not_connecting(&callback)
|
153
|
-
if @connecting
|
154
|
-
@connected_future.on_complete(&callback)
|
155
|
-
@connected_future.on_failure(&callback)
|
156
|
-
else
|
157
|
-
callback.call
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
def when_not_closing(&callback)
|
162
|
-
if @closing
|
163
|
-
@closed_future.on_complete(&callback)
|
164
|
-
@closed_future.on_failure(&callback)
|
165
|
-
else
|
166
|
-
callback.call
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
def discover_peers(seed_connections, initial_keyspace)
|
171
|
-
connected_seeds = seed_connections.select(&:connected?)
|
172
|
-
connection = connected_seeds.sample
|
173
|
-
return Future.completed([]) unless connection
|
174
|
-
request = Protocol::QueryRequest.new('SELECT peer, data_center, host_id, rpc_address FROM system.peers', :one)
|
175
|
-
peer_info = execute_request(request, connection)
|
176
|
-
peer_info.flat_map do |result|
|
177
|
-
seed_dcs = connected_seeds.map { |c| c[:data_center] }.uniq
|
178
|
-
unconnected_peers = result.select do |row|
|
179
|
-
seed_dcs.include?(row['data_center']) && connected_seeds.none? { |c| c[:host_id] == row['host_id'] }
|
118
|
+
def connected(f)
|
119
|
+
if f.resolved?
|
120
|
+
@lock.synchronize do
|
121
|
+
@connecting = false
|
122
|
+
@connected = true
|
180
123
|
end
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
rpc_address
|
187
|
-
end
|
124
|
+
@logger.info('Cluster connection complete')
|
125
|
+
else
|
126
|
+
@lock.synchronize do
|
127
|
+
@connecting = false
|
128
|
+
@connected = false
|
188
129
|
end
|
189
|
-
|
190
|
-
|
191
|
-
else
|
192
|
-
Future.completed([])
|
130
|
+
f.on_failure do |e|
|
131
|
+
@logger.error('Failed connecting to cluster: %s' % e.message)
|
193
132
|
end
|
133
|
+
close
|
194
134
|
end
|
195
135
|
end
|
196
136
|
|
197
|
-
def
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
end
|
204
|
-
f.on_complete do |connections|
|
205
|
-
connected_connections = connections.select(&:connected?)
|
206
|
-
if connected_connections.any?
|
207
|
-
@connections = connected_connections
|
208
|
-
@connected_future.complete!(self)
|
137
|
+
def closed(f)
|
138
|
+
@lock.synchronize do
|
139
|
+
@closing = false
|
140
|
+
@connected = false
|
141
|
+
if f.resolved?
|
142
|
+
@logger.info('Cluster disconnect complete')
|
209
143
|
else
|
210
|
-
|
211
|
-
|
212
|
-
end
|
213
|
-
end
|
214
|
-
|
215
|
-
def fail_connecting(e)
|
216
|
-
close
|
217
|
-
if e.is_a?(Cql::QueryError) && e.code == 0x100
|
218
|
-
@connected_future.fail!(AuthenticationError.new(e.message))
|
219
|
-
else
|
220
|
-
@connected_future.fail!(e)
|
221
|
-
end
|
222
|
-
end
|
223
|
-
|
224
|
-
def connect_to_hosts(hosts, initial_keyspace, peer_discovery)
|
225
|
-
connection_futures = hosts.map do |host|
|
226
|
-
connect_to_host(host, initial_keyspace).recover do |error|
|
227
|
-
FailedConnection.new(error)
|
228
|
-
end
|
229
|
-
end
|
230
|
-
hosts_connected_future = Future.combine(*connection_futures)
|
231
|
-
if peer_discovery
|
232
|
-
hosts_connected_future.flat_map do |connections|
|
233
|
-
discover_peers(connections, initial_keyspace).map do |peer_connections|
|
234
|
-
connections + peer_connections
|
144
|
+
f.on_failure do |e|
|
145
|
+
@logger.error('Cluster disconnect failed: %s' % e.message)
|
235
146
|
end
|
236
147
|
end
|
237
|
-
else
|
238
|
-
hosts_connected_future
|
239
148
|
end
|
240
149
|
end
|
241
150
|
|
242
|
-
def
|
243
|
-
|
244
|
-
connected.flat_map do |connection|
|
245
|
-
initialize_connection(connection, keyspace)
|
246
|
-
end
|
151
|
+
def can_execute?
|
152
|
+
!@closing && (@connecting || (@connected && @connection_manager.connected?))
|
247
153
|
end
|
248
154
|
|
249
|
-
def
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
155
|
+
def with_failure_handler
|
156
|
+
return Future.failed(NotConnectedError.new) unless can_execute?
|
157
|
+
yield
|
158
|
+
rescue => e
|
159
|
+
Future.failed(e)
|
254
160
|
end
|
255
161
|
|
256
|
-
def
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
162
|
+
def register_event_listener(connection)
|
163
|
+
register_request = Protocol::RegisterRequest.new(Protocol::TopologyChangeEventResponse::TYPE, Protocol::StatusChangeEventResponse::TYPE)
|
164
|
+
execute_request(register_request, connection)
|
165
|
+
connection.on_closed do
|
166
|
+
if connected?
|
167
|
+
begin
|
168
|
+
register_event_listener(@connection_manager.random_connection)
|
169
|
+
rescue NotConnectedError
|
170
|
+
# we had started closing down after the connection check
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
connection.on_event do |event|
|
175
|
+
begin
|
176
|
+
if event.change == 'UP'
|
177
|
+
@logger.debug('Received UP event')
|
178
|
+
handle_topology_change
|
179
|
+
end
|
263
180
|
end
|
264
181
|
end
|
265
|
-
f
|
266
|
-
end
|
267
|
-
|
268
|
-
def use_keyspace(keyspace, connection)
|
269
|
-
return Future.completed(connection) unless keyspace
|
270
|
-
return Future.failed(InvalidKeyspaceNameError.new(%("#{keyspace}" is not a valid keyspace name))) unless valid_keyspace_name?(keyspace)
|
271
|
-
execute_request(Protocol::QueryRequest.new("USE #{keyspace}", :one), connection).map { connection }
|
272
182
|
end
|
273
183
|
|
274
|
-
def
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
184
|
+
def handle_topology_change
|
185
|
+
seed_connections = @connection_manager.snapshot
|
186
|
+
f = @connection_helper.discover_peers(seed_connections, keyspace)
|
187
|
+
f.on_value do |connections|
|
188
|
+
connected_connections = connections.select(&:connected?)
|
189
|
+
if connected_connections.any?
|
190
|
+
@connection_manager.add_connections(connected_connections)
|
280
191
|
else
|
281
|
-
|
192
|
+
@logger.debug('Scheduling new peer discovery in 1s')
|
193
|
+
f = @io_reactor.schedule_timer(1)
|
194
|
+
f.on_value do
|
195
|
+
handle_topology_change
|
196
|
+
end
|
282
197
|
end
|
283
|
-
else
|
284
|
-
Future.completed(connection)
|
285
198
|
end
|
286
199
|
end
|
287
200
|
|
288
201
|
def execute_request(request, connection=nil)
|
289
|
-
f = @request_runner.execute(connection || @
|
202
|
+
f = @request_runner.execute(connection || @connection_manager.random_connection, request)
|
290
203
|
f.map do |result|
|
291
204
|
if result.is_a?(KeyspaceChanged)
|
292
205
|
use(result.keyspace)
|
@@ -4,22 +4,64 @@ module Cql
|
|
4
4
|
module Client
|
5
5
|
# @private
|
6
6
|
class AsynchronousPreparedStatement < PreparedStatement
|
7
|
-
|
8
|
-
|
9
|
-
@
|
10
|
-
@
|
11
|
-
@
|
7
|
+
# @private
|
8
|
+
def initialize(cql, default_consistency, connection_manager, logger)
|
9
|
+
@cql = cql
|
10
|
+
@default_consistency = default_consistency
|
11
|
+
@connection_manager = connection_manager
|
12
|
+
@logger = logger
|
12
13
|
@request_runner = RequestRunner.new
|
13
14
|
end
|
14
15
|
|
16
|
+
def self.prepare(cql, default_consistency, connection_manager, logger)
|
17
|
+
statement = new(cql, default_consistency, connection_manager, logger)
|
18
|
+
futures = connection_manager.map do |connection|
|
19
|
+
statement.prepare(connection)
|
20
|
+
end
|
21
|
+
Future.all(*futures).map { statement }
|
22
|
+
rescue => e
|
23
|
+
Future.failed(e)
|
24
|
+
end
|
25
|
+
|
15
26
|
def execute(*args)
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
27
|
+
connection = @connection_manager.random_connection
|
28
|
+
if connection[self]
|
29
|
+
run(args, connection)
|
30
|
+
else
|
31
|
+
prepare(connection).flat_map do
|
32
|
+
run(args, connection)
|
33
|
+
end
|
34
|
+
end
|
20
35
|
rescue => e
|
21
36
|
Future.failed(e)
|
22
37
|
end
|
38
|
+
|
39
|
+
# @private
|
40
|
+
def prepare(connection)
|
41
|
+
prepare_request = Protocol::PrepareRequest.new(@cql)
|
42
|
+
f = @request_runner.execute(connection, prepare_request) do |response|
|
43
|
+
connection[self] = response.id
|
44
|
+
unless @raw_metadata
|
45
|
+
# NOTE: this is not thread safe, but the worst that could happen
|
46
|
+
# is that we assign the same data multiple times
|
47
|
+
@raw_metadata = response.metadata
|
48
|
+
@metadata = ResultMetadata.new(@raw_metadata)
|
49
|
+
end
|
50
|
+
@logger.debug('Statement prepared on new connection')
|
51
|
+
end
|
52
|
+
f.map { self }
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def run(args, connection)
|
58
|
+
statement_id = connection[self]
|
59
|
+
bound_args = args.shift(@raw_metadata.size)
|
60
|
+
consistency = args.shift || @default_consistency
|
61
|
+
statement_id = connection[self]
|
62
|
+
request = Protocol::ExecuteRequest.new(statement_id, @raw_metadata, bound_args, consistency)
|
63
|
+
@request_runner.execute(connection, request)
|
64
|
+
end
|
23
65
|
end
|
24
66
|
end
|
25
67
|
end
|