cassandra-driver 1.0.0.rc.1 → 1.0.0
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.
- checksums.yaml +8 -8
- data/README.md +45 -10
- data/lib/cassandra.rb +82 -82
- data/lib/cassandra/cluster.rb +3 -0
- data/lib/cassandra/cluster/client.rb +17 -5
- data/lib/cassandra/{client/connection_manager.rb → cluster/connection_pool.rb} +3 -3
- data/lib/cassandra/cluster/connector.rb +101 -30
- data/lib/cassandra/cluster/control_connection.rb +6 -7
- data/lib/cassandra/{client/null_logger.rb → cluster/failed_connection.rb} +12 -14
- data/lib/cassandra/cluster/options.rb +8 -0
- data/lib/cassandra/column.rb +5 -0
- data/lib/cassandra/driver.rb +3 -3
- data/lib/cassandra/errors.rb +5 -5
- data/lib/cassandra/execution/options.rb +13 -6
- data/lib/cassandra/execution/trace.rb +4 -4
- data/lib/cassandra/future.rb +4 -0
- data/lib/cassandra/keyspace.rb +5 -0
- data/lib/cassandra/load_balancing/policies/dc_aware_round_robin.rb +7 -2
- data/lib/cassandra/load_balancing/policies/token_aware.rb +1 -3
- data/lib/cassandra/load_balancing/policies/white_list.rb +3 -6
- data/lib/cassandra/null_logger.rb +35 -0
- data/lib/cassandra/protocol/cql_protocol_handler.rb +4 -0
- data/lib/cassandra/protocol/requests/query_request.rb +1 -11
- data/lib/cassandra/result.rb +4 -6
- data/lib/cassandra/session.rb +3 -0
- data/lib/cassandra/statements/prepared.rb +5 -1
- data/lib/cassandra/table.rb +5 -0
- data/lib/cassandra/util.rb +130 -0
- data/lib/cassandra/version.rb +1 -1
- metadata +9 -20
- data/lib/cassandra/client.rb +0 -144
- data/lib/cassandra/client/batch.rb +0 -212
- data/lib/cassandra/client/client.rb +0 -591
- data/lib/cassandra/client/column_metadata.rb +0 -54
- data/lib/cassandra/client/connector.rb +0 -273
- data/lib/cassandra/client/execute_options_decoder.rb +0 -59
- data/lib/cassandra/client/peer_discovery.rb +0 -50
- data/lib/cassandra/client/prepared_statement.rb +0 -314
- data/lib/cassandra/client/query_result.rb +0 -230
- data/lib/cassandra/client/request_runner.rb +0 -70
- data/lib/cassandra/client/result_metadata.rb +0 -48
- data/lib/cassandra/client/void_result.rb +0 -78
@@ -1,54 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
#--
|
4
|
-
# Copyright 2013-2014 DataStax, Inc.
|
5
|
-
#
|
6
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
-
# you may not use this file except in compliance with the License.
|
8
|
-
# You may obtain a copy of the License at
|
9
|
-
#
|
10
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
-
#
|
12
|
-
# Unless required by applicable law or agreed to in writing, software
|
13
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
-
# See the License for the specific language governing permissions and
|
16
|
-
# limitations under the License.
|
17
|
-
#++
|
18
|
-
|
19
|
-
module Cassandra
|
20
|
-
module Client
|
21
|
-
# Represents metadata about a column in a query result set or prepared
|
22
|
-
# statement. Apart from the keyspace, table and column names there's also
|
23
|
-
# the type as a symbol (e.g. `:varchar`, `:int`, `:date`).
|
24
|
-
class ColumnMetadata
|
25
|
-
attr_reader :keyspace, :table, :column_name, :type
|
26
|
-
|
27
|
-
# @private
|
28
|
-
def initialize(*args)
|
29
|
-
@keyspace, @table, @column_name, @type = args
|
30
|
-
end
|
31
|
-
|
32
|
-
# @private
|
33
|
-
def to_ary
|
34
|
-
[@keyspace, @table, @column_name, @type]
|
35
|
-
end
|
36
|
-
|
37
|
-
def eql?(other)
|
38
|
-
self.keyspace == other.keyspace && self.table == other.table && self.column_name == other.column_name && self.type == other.type
|
39
|
-
end
|
40
|
-
alias_method :==, :eql?
|
41
|
-
|
42
|
-
def hash
|
43
|
-
@h ||= begin
|
44
|
-
h = 0
|
45
|
-
h = ((h & 33554431) * 31) ^ @keyspace.hash
|
46
|
-
h = ((h & 33554431) * 31) ^ @table.hash
|
47
|
-
h = ((h & 33554431) * 31) ^ @column_name.hash
|
48
|
-
h = ((h & 33554431) * 31) ^ @type.hash
|
49
|
-
h
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
@@ -1,273 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
#--
|
4
|
-
# Copyright 2013-2014 DataStax, Inc.
|
5
|
-
#
|
6
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
-
# you may not use this file except in compliance with the License.
|
8
|
-
# You may obtain a copy of the License at
|
9
|
-
#
|
10
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
-
#
|
12
|
-
# Unless required by applicable law or agreed to in writing, software
|
13
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
-
# See the License for the specific language governing permissions and
|
16
|
-
# limitations under the License.
|
17
|
-
#++
|
18
|
-
|
19
|
-
module Cassandra
|
20
|
-
module Client
|
21
|
-
# @private
|
22
|
-
class ClusterConnector
|
23
|
-
def initialize(sequence, logger)
|
24
|
-
@sequence = sequence
|
25
|
-
@logger = logger
|
26
|
-
end
|
27
|
-
|
28
|
-
def connect_all(hosts, connections_per_node)
|
29
|
-
connections = hosts.flat_map do |host|
|
30
|
-
Array.new(connections_per_node) do
|
31
|
-
f = @sequence.connect(host)
|
32
|
-
f.on_value { |connection| register_logging(connection) }
|
33
|
-
f.recover do |error|
|
34
|
-
@logger.warn('Failed connecting to node at %s: %s' % [host, error.message])
|
35
|
-
FailedConnection.new(error, host)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
Ione::Future.all(*connections).map do |connections|
|
40
|
-
connected_connections = connections.select(&:connected?)
|
41
|
-
if connected_connections.empty?
|
42
|
-
error = connections.first.error
|
43
|
-
raise(error, error.message, error.backtrace)
|
44
|
-
end
|
45
|
-
connected_connections
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
private
|
50
|
-
|
51
|
-
def register_logging(connection)
|
52
|
-
args = [connection[:host_id], connection.host, connection.port, connection[:data_center]]
|
53
|
-
@logger.info('Connected to node %s at %s:%d in data center %s' % args)
|
54
|
-
connection.on_closed do |cause|
|
55
|
-
message = 'Connection to node %s at %s:%d in data center %s closed' % args
|
56
|
-
if cause
|
57
|
-
message << (' unexpectedly: %s' % cause.message)
|
58
|
-
@logger.warn(message)
|
59
|
-
else
|
60
|
-
@logger.info(message)
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
# @private
|
67
|
-
class Connector
|
68
|
-
def initialize(steps)
|
69
|
-
@steps = steps.dup
|
70
|
-
end
|
71
|
-
|
72
|
-
def connect(host)
|
73
|
-
pending_connection = PendingConnection.new(host)
|
74
|
-
seed = Ione::Future.resolved(pending_connection)
|
75
|
-
f = @steps.reduce(seed) do |chain, step|
|
76
|
-
chain.flat_map do |pending_connection|
|
77
|
-
step.run(pending_connection)
|
78
|
-
end
|
79
|
-
end
|
80
|
-
f.map do |pending_connection|
|
81
|
-
pending_connection.connection
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
# @private
|
87
|
-
class ConnectStep
|
88
|
-
def initialize(io_reactor, protocol_handler_factory, port, connection_options, logger)
|
89
|
-
@io_reactor = io_reactor
|
90
|
-
@protocol_handler_factory = protocol_handler_factory
|
91
|
-
@port = port
|
92
|
-
@connection_options = connection_options
|
93
|
-
@logger = logger
|
94
|
-
end
|
95
|
-
|
96
|
-
def run(pending_connection)
|
97
|
-
@io_reactor.connect(pending_connection.host, @port, @connection_options, &@protocol_handler_factory).map do |connection|
|
98
|
-
pending_connection.with_connection(connection)
|
99
|
-
end
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
# @private
|
104
|
-
class CacheOptionsStep
|
105
|
-
def initialize(timeout = nil)
|
106
|
-
@timeout = timeout
|
107
|
-
end
|
108
|
-
|
109
|
-
def run(pending_connection)
|
110
|
-
f = pending_connection.execute(Protocol::OptionsRequest.new, @timeout)
|
111
|
-
f.on_value do |supported_options|
|
112
|
-
pending_connection[:cql_version] = supported_options['CQL_VERSION']
|
113
|
-
pending_connection[:compression] = supported_options['COMPRESSION']
|
114
|
-
end
|
115
|
-
|
116
|
-
f.map(pending_connection)
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
# @private
|
121
|
-
class InitializeStep
|
122
|
-
def initialize(compressor, logger)
|
123
|
-
@compressor = compressor
|
124
|
-
@logger = logger
|
125
|
-
end
|
126
|
-
|
127
|
-
def run(pending_connection)
|
128
|
-
compression = @compressor && @compressor.algorithm
|
129
|
-
supported_algorithms = pending_connection[:compression]
|
130
|
-
if compression && !supported_algorithms.include?(compression)
|
131
|
-
@logger.info("Compression with #{compression.inspect} is not supported by host at #{pending_connection.host}, supported algorithms are #{supported_algorithms.inspect}")
|
132
|
-
compression = nil
|
133
|
-
end
|
134
|
-
supported_cql_versions = pending_connection[:cql_version]
|
135
|
-
cql_version = (supported_cql_versions && !supported_cql_versions.empty?) ? supported_cql_versions.first : '3.1.0'
|
136
|
-
|
137
|
-
f = pending_connection.execute(Protocol::StartupRequest.new(cql_version, compression))
|
138
|
-
f.map do |startup_response|
|
139
|
-
if startup_response.is_a?(AuthenticationRequired)
|
140
|
-
pending_connection.with_authentication_class(startup_response.authentication_class)
|
141
|
-
else
|
142
|
-
pending_connection
|
143
|
-
end
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
# @private
|
149
|
-
class SaslAuthenticationStep
|
150
|
-
def initialize(auth_provider)
|
151
|
-
@auth_provider = auth_provider
|
152
|
-
end
|
153
|
-
|
154
|
-
def run(pending_connection)
|
155
|
-
if pending_connection.authentication_class
|
156
|
-
begin
|
157
|
-
authenticator = @auth_provider && @auth_provider.create_authenticator(pending_connection.authentication_class)
|
158
|
-
if authenticator
|
159
|
-
token = authenticator.initial_response
|
160
|
-
challenge_cycle(pending_connection, authenticator, token)
|
161
|
-
elsif @auth_provider
|
162
|
-
Ione::Future.failed(Errors::AuthenticationError.new('Auth provider does not support the required authentication class "%s" and/or protocol version %d' % [pending_connection.authentication_class, @protocol_version]))
|
163
|
-
else
|
164
|
-
Ione::Future.failed(Errors::AuthenticationError.new('Server requested authentication, but client was not configured to authenticate'))
|
165
|
-
end
|
166
|
-
rescue => e
|
167
|
-
Ione::Future.failed(Errors::AuthenticationError.new('Auth provider error (%s: %s)' % [e.class.name, e.message]))
|
168
|
-
end
|
169
|
-
else
|
170
|
-
Ione::Future.resolved(pending_connection)
|
171
|
-
end
|
172
|
-
end
|
173
|
-
|
174
|
-
def challenge_cycle(pending_connection, authenticator, response_token)
|
175
|
-
request = Protocol::AuthResponseRequest.new(response_token)
|
176
|
-
f = pending_connection.execute(request) { |raw_response| raw_response }
|
177
|
-
f.flat_map do |response|
|
178
|
-
case response
|
179
|
-
when Protocol::AuthChallengeResponse
|
180
|
-
token = authenticator.challenge_response(response.token)
|
181
|
-
challenge_cycle(pending_connection, authenticator, token)
|
182
|
-
when Protocol::AuthSuccessResponse
|
183
|
-
authenticator.authentication_successful(response.token) rescue nil
|
184
|
-
Ione::Future.resolved(pending_connection)
|
185
|
-
else
|
186
|
-
Ione::Future.resolved(pending_connection)
|
187
|
-
end
|
188
|
-
end
|
189
|
-
end
|
190
|
-
end
|
191
|
-
|
192
|
-
# @private
|
193
|
-
class CredentialsAuthenticationStep
|
194
|
-
def initialize(credentials)
|
195
|
-
@credentials = credentials
|
196
|
-
end
|
197
|
-
|
198
|
-
def run(pending_connection)
|
199
|
-
if pending_connection.authentication_class
|
200
|
-
if @credentials
|
201
|
-
request = Protocol::CredentialsRequest.new(@credentials)
|
202
|
-
pending_connection.execute(request).map(pending_connection)
|
203
|
-
else
|
204
|
-
Ione::Future.failed(Errors::AuthenticationError.new('Server requested authentication, but client was not configured to authenticate'))
|
205
|
-
end
|
206
|
-
else
|
207
|
-
Ione::Future.resolved(pending_connection)
|
208
|
-
end
|
209
|
-
end
|
210
|
-
end
|
211
|
-
|
212
|
-
# @private
|
213
|
-
class CachePropertiesStep
|
214
|
-
def run(pending_connection)
|
215
|
-
request = Protocol::QueryRequest.new('SELECT data_center, host_id FROM system.local', nil, nil, :one)
|
216
|
-
f = pending_connection.execute(request)
|
217
|
-
f.on_value do |result|
|
218
|
-
unless result.empty?
|
219
|
-
pending_connection[:host_id] = result.first['host_id']
|
220
|
-
pending_connection[:data_center] = result.first['data_center']
|
221
|
-
end
|
222
|
-
end
|
223
|
-
f.map(pending_connection)
|
224
|
-
end
|
225
|
-
end
|
226
|
-
|
227
|
-
# @private
|
228
|
-
class PendingConnection
|
229
|
-
attr_reader :host, :connection, :authentication_class
|
230
|
-
|
231
|
-
def initialize(host, connection=nil, authentication_class=nil)
|
232
|
-
@host = host
|
233
|
-
@connection = connection
|
234
|
-
@authentication_class = authentication_class
|
235
|
-
@request_runner = RequestRunner.new
|
236
|
-
end
|
237
|
-
|
238
|
-
def with_connection(connection)
|
239
|
-
self.class.new(host, connection, @authentication_class)
|
240
|
-
end
|
241
|
-
|
242
|
-
def with_authentication_class(authentication_class)
|
243
|
-
self.class.new(host, @connection, authentication_class)
|
244
|
-
end
|
245
|
-
|
246
|
-
def [](key)
|
247
|
-
@connection[key]
|
248
|
-
end
|
249
|
-
|
250
|
-
def []=(key, value)
|
251
|
-
@connection[key] = value
|
252
|
-
end
|
253
|
-
|
254
|
-
def execute(request, timeout = nil, &block)
|
255
|
-
@request_runner.execute(@connection, request, timeout, nil, &block)
|
256
|
-
end
|
257
|
-
end
|
258
|
-
|
259
|
-
# @private
|
260
|
-
class FailedConnection
|
261
|
-
attr_reader :error, :host
|
262
|
-
|
263
|
-
def initialize(error, host)
|
264
|
-
@error = error
|
265
|
-
@host = host
|
266
|
-
end
|
267
|
-
|
268
|
-
def connected?
|
269
|
-
false
|
270
|
-
end
|
271
|
-
end
|
272
|
-
end
|
273
|
-
end
|
@@ -1,59 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
#--
|
4
|
-
# Copyright 2013-2014 DataStax, Inc.
|
5
|
-
#
|
6
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
-
# you may not use this file except in compliance with the License.
|
8
|
-
# You may obtain a copy of the License at
|
9
|
-
#
|
10
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
-
#
|
12
|
-
# Unless required by applicable law or agreed to in writing, software
|
13
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
-
# See the License for the specific language governing permissions and
|
16
|
-
# limitations under the License.
|
17
|
-
#++
|
18
|
-
|
19
|
-
module Cassandra
|
20
|
-
module Client
|
21
|
-
# @private
|
22
|
-
class ExecuteOptionsDecoder
|
23
|
-
def initialize(default_consistency)
|
24
|
-
@default_consistency = default_consistency
|
25
|
-
@default_options = {:consistency => @default_consistency}.freeze
|
26
|
-
end
|
27
|
-
|
28
|
-
def decode_options(*args)
|
29
|
-
if args.empty?
|
30
|
-
@default_options
|
31
|
-
elsif args.size == 1
|
32
|
-
decode_one(args.first)
|
33
|
-
else
|
34
|
-
args.each_with_object({}) do |options_or_consistency, result|
|
35
|
-
result.merge!(decode_one(options_or_consistency))
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
private
|
41
|
-
|
42
|
-
def decode_one(options_or_consistency)
|
43
|
-
return @default_options unless options_or_consistency
|
44
|
-
case options_or_consistency
|
45
|
-
when Symbol
|
46
|
-
{:consistency => options_or_consistency}
|
47
|
-
when Hash
|
48
|
-
if options_or_consistency.include?(:consistency)
|
49
|
-
options_or_consistency
|
50
|
-
else
|
51
|
-
options = options_or_consistency.dup
|
52
|
-
options[:consistency] = @default_consistency
|
53
|
-
options
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
@@ -1,50 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
#--
|
4
|
-
# Copyright 2013-2014 DataStax, Inc.
|
5
|
-
#
|
6
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
-
# you may not use this file except in compliance with the License.
|
8
|
-
# You may obtain a copy of the License at
|
9
|
-
#
|
10
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
-
#
|
12
|
-
# Unless required by applicable law or agreed to in writing, software
|
13
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
-
# See the License for the specific language governing permissions and
|
16
|
-
# limitations under the License.
|
17
|
-
#++
|
18
|
-
|
19
|
-
module Cassandra
|
20
|
-
module Client
|
21
|
-
# @private
|
22
|
-
class PeerDiscovery
|
23
|
-
def initialize(seed_connections)
|
24
|
-
@seed_connections = seed_connections
|
25
|
-
@connection = seed_connections.sample
|
26
|
-
@request_runner = RequestRunner.new
|
27
|
-
end
|
28
|
-
|
29
|
-
def new_hosts
|
30
|
-
request = Protocol::QueryRequest.new('SELECT peer, data_center, host_id, rpc_address FROM system.peers', nil, nil, :one)
|
31
|
-
response = @request_runner.execute(@connection, request)
|
32
|
-
response.map do |result|
|
33
|
-
result.each_with_object([]) do |row, new_peers|
|
34
|
-
if include?(row['host_id'], row['data_center'])
|
35
|
-
rpc_address = row['rpc_address'].to_s
|
36
|
-
rpc_address = row['peer'].to_s if rpc_address == '0.0.0.0'
|
37
|
-
new_peers << rpc_address
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
private
|
44
|
-
|
45
|
-
def include?(host_id, dc)
|
46
|
-
@seed_connections.any? { |c| c[:data_center] == dc } && @seed_connections.none? { |c| c[:host_id] == host_id }
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
@@ -1,314 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
#--
|
4
|
-
# Copyright 2013-2014 DataStax, Inc.
|
5
|
-
#
|
6
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
-
# you may not use this file except in compliance with the License.
|
8
|
-
# You may obtain a copy of the License at
|
9
|
-
#
|
10
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
-
#
|
12
|
-
# Unless required by applicable law or agreed to in writing, software
|
13
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
-
# See the License for the specific language governing permissions and
|
16
|
-
# limitations under the License.
|
17
|
-
#++
|
18
|
-
|
19
|
-
module Cassandra
|
20
|
-
module Client
|
21
|
-
# A prepared statement are CQL queries that have been sent to the server
|
22
|
-
# to be precompiled, so that when executed only their ID and not the whole
|
23
|
-
# CQL string need to be sent. They support bound values, or placeholders
|
24
|
-
# for values.
|
25
|
-
#
|
26
|
-
# Using a prepared statement for any query that you execute more than once
|
27
|
-
# is highly recommended. Besides the benefit of having less network overhead,
|
28
|
-
# and less processing overhead on the server side, they don't require you
|
29
|
-
# to build CQL strings and escape special characters, or format non-character
|
30
|
-
# data such as UUIDs, different numeric types, or collections, in the
|
31
|
-
# correct way.
|
32
|
-
#
|
33
|
-
# You should only prepare a statement once and reuse the prepared statement
|
34
|
-
# object every time you want to execute that particular query. The statement
|
35
|
-
# object will make sure that it is prepared on all connections, and will
|
36
|
-
# (lazily, but transparently) make sure it is prepared on any new connections.
|
37
|
-
#
|
38
|
-
# It is an anti-pattern to prepare the same query over and over again. It is
|
39
|
-
# bad for performance, since every preparation requires a roundtrip to all
|
40
|
-
# connected servers, and because of some bookeeping that is done to support
|
41
|
-
# automatic preparation on new connections, it will lead to unnecessary
|
42
|
-
# extra memory usage. There is no performance benefit in creating multiple
|
43
|
-
# prepared statement objects for the same query.
|
44
|
-
#
|
45
|
-
# Prepared statement objects are completely thread safe and can be shared
|
46
|
-
# across all threads in your application.
|
47
|
-
#
|
48
|
-
# @see Cassandra::Client::Client#prepare
|
49
|
-
class PreparedStatement
|
50
|
-
# Metadata describing the bound values
|
51
|
-
#
|
52
|
-
# @return [ResultMetadata]
|
53
|
-
attr_reader :metadata
|
54
|
-
|
55
|
-
# Metadata about the result (i.e. rows) that is returned when executing
|
56
|
-
# this prepared statement.
|
57
|
-
#
|
58
|
-
# @return [ResultMetadata]
|
59
|
-
attr_reader :result_metadata
|
60
|
-
|
61
|
-
# Execute the prepared statement with a list of values to be bound to the
|
62
|
-
# statements parameters.
|
63
|
-
#
|
64
|
-
# The number of arguments must equal the number of bound parameters. You
|
65
|
-
# can also specify options as the last argument, or a symbol as a shortcut
|
66
|
-
# for just specifying the consistency.
|
67
|
-
#
|
68
|
-
# Because you can specify options, or not, there is an edge case where if
|
69
|
-
# the last parameter of your prepared statement is a map, and you forget
|
70
|
-
# to specify a value for your map, the options will end up being sent to
|
71
|
-
# Cassandra. Most other cases when you specify the wrong number of
|
72
|
-
# arguments should result in an `ArgumentError` or `TypeError` being
|
73
|
-
# raised.
|
74
|
-
#
|
75
|
-
# @example Preparing and executing an `INSERT` statement
|
76
|
-
# statement = client.prepare(%(INSERT INTO metrics (id, time, value) VALUES (?, NOW(), ?)))
|
77
|
-
# statement.execute(1234, 23432)
|
78
|
-
# statement.execute(2345, 34543, tracing: true)
|
79
|
-
# statement.execute(3456, 45654, consistency: :one)
|
80
|
-
#
|
81
|
-
# @example Preparing and executing a `SELECT` statement
|
82
|
-
# statement = client.prepare(%(SELECT * FROM metrics WHERE id = ? AND time > ?))
|
83
|
-
# result = statement.execute(1234, Time.now - 3600)
|
84
|
-
# result.each do |row|
|
85
|
-
# p row
|
86
|
-
# end
|
87
|
-
#
|
88
|
-
# @param args [Array] the values for the bound parameters, and an optional
|
89
|
-
# hash of options as last argument – see {Cassandra::Client::Client#execute}
|
90
|
-
# for details on which options are available.
|
91
|
-
# @raise [ArgumentError] raised when number of argument does not match
|
92
|
-
# the number of parameters needed to be bound to the statement.
|
93
|
-
# @raise [Cassandra::Errors::ClientError] raised when the client is not connected
|
94
|
-
# @raise [Cassandra::Errors::IoError] raised when there is an IO error, for example
|
95
|
-
# if the server suddenly closes the connection
|
96
|
-
# @raise [Cassandra::Errors::QueryError] raised when there is an error on the server side
|
97
|
-
# @return [nil, Cassandra::Client::QueryResult, Cassandra::Client::VoidResult] Some
|
98
|
-
# queries have no result and return `nil`, but `SELECT` statements
|
99
|
-
# return an `Enumerable` of rows (see {Cassandra::Client::QueryResult}), and
|
100
|
-
# `INSERT` and `UPDATE` return a similar type
|
101
|
-
# (see {Cassandra::Client::VoidResult}).
|
102
|
-
def execute(*args)
|
103
|
-
end
|
104
|
-
|
105
|
-
# Yields a batch when called with a block. The batch is automatically
|
106
|
-
# executed at the end of the block and the result is returned.
|
107
|
-
#
|
108
|
-
# Returns a batch when called wihtout a block. The batch will remember
|
109
|
-
# the options given and merge these with any additional options given
|
110
|
-
# when {Cassandra::Client::PreparedStatementBatch#execute} is called.
|
111
|
-
#
|
112
|
-
# The batch yielded or returned by this method is not identical to the
|
113
|
-
# regular batch objects yielded or returned by {Cassandra::Client::Client#batch}.
|
114
|
-
# These prepared statement batch objects can be used only to add multiple
|
115
|
-
# executions of the same prepared statement.
|
116
|
-
#
|
117
|
-
# Please note that the batch object returned by this method _is not thread
|
118
|
-
# safe_.
|
119
|
-
#
|
120
|
-
# The type parameter can be ommitted and the options can then be given
|
121
|
-
# as first parameter.
|
122
|
-
#
|
123
|
-
# @example Executing a prepared statement in a batch
|
124
|
-
# statement = client.prepare(%(INSERT INTO metrics (id, time, value) VALUES (?, NOW(), ?)))
|
125
|
-
# statement.batch do |batch|
|
126
|
-
# batch.add(1234, 23423)
|
127
|
-
# batch.add(2346, 13)
|
128
|
-
# batch.add(2342, 2367)
|
129
|
-
# batch.add(4562, 1231)
|
130
|
-
# end
|
131
|
-
#
|
132
|
-
# @see Cassandra::Client::PreparedStatementBatch
|
133
|
-
# @see Cassandra::Client::Client#batch
|
134
|
-
# @param [Symbol] type the type of batch, must be one of `:logged`,
|
135
|
-
# `:unlogged` and `:counter`. The precise meaning of these is defined
|
136
|
-
# in the CQL specification.
|
137
|
-
# @yieldparam [Cassandra::Client::PreparedStatementBatch] batch the batch
|
138
|
-
# @return [Cassandra::Client::VoidResult, Cassandra::Client::Batch] when no block is
|
139
|
-
# given the batch is returned, when a block is given the result of
|
140
|
-
# executing the batch is returned (see {Cassandra::Client::Batch#execute}).
|
141
|
-
def batch(type=:logged, options={})
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
# @private
|
146
|
-
class AsynchronousPreparedStatement < PreparedStatement
|
147
|
-
# @private
|
148
|
-
def initialize(cql, execute_options_decoder, connection_manager, logger)
|
149
|
-
@cql = cql
|
150
|
-
@execute_options_decoder = execute_options_decoder
|
151
|
-
@connection_manager = connection_manager
|
152
|
-
@logger = logger
|
153
|
-
@request_runner = RequestRunner.new
|
154
|
-
end
|
155
|
-
|
156
|
-
def self.prepare(cql, execute_options_decoder, connection_manager, logger)
|
157
|
-
statement = new(cql, execute_options_decoder, connection_manager, logger)
|
158
|
-
futures = connection_manager.map do |connection|
|
159
|
-
statement.prepare(connection)
|
160
|
-
end
|
161
|
-
Ione::Future.all(*futures).map(statement)
|
162
|
-
rescue => e
|
163
|
-
Ione::Future.failed(e)
|
164
|
-
end
|
165
|
-
|
166
|
-
def execute(*args)
|
167
|
-
connection = @connection_manager.random_connection
|
168
|
-
if connection[self]
|
169
|
-
run(args, connection)
|
170
|
-
else
|
171
|
-
prepare(connection).flat_map do
|
172
|
-
run(args, connection)
|
173
|
-
end
|
174
|
-
end
|
175
|
-
rescue => e
|
176
|
-
Ione::Future.failed(e)
|
177
|
-
end
|
178
|
-
|
179
|
-
def batch(type=:logged, options=nil)
|
180
|
-
if type.is_a?(Hash)
|
181
|
-
options = type
|
182
|
-
type = :logged
|
183
|
-
end
|
184
|
-
b = AsynchronousBatch.new(type, @execute_options_decoder, @connection_manager, options)
|
185
|
-
pb = AsynchronousPreparedStatementBatch.new(self, b)
|
186
|
-
if block_given?
|
187
|
-
yield pb
|
188
|
-
pb.execute
|
189
|
-
else
|
190
|
-
pb
|
191
|
-
end
|
192
|
-
end
|
193
|
-
|
194
|
-
# @private
|
195
|
-
def prepare(connection)
|
196
|
-
prepare_request = Protocol::PrepareRequest.new(@cql)
|
197
|
-
f = @request_runner.execute(connection, prepare_request) do |response|
|
198
|
-
connection[self] = response.id
|
199
|
-
unless @raw_metadata
|
200
|
-
# NOTE: this is not thread safe, but the worst that could happen
|
201
|
-
# is that we assign the same data multiple times
|
202
|
-
@raw_metadata = response.metadata
|
203
|
-
@metadata = ResultMetadata.new(@raw_metadata)
|
204
|
-
@raw_result_metadata = response.result_metadata
|
205
|
-
if @raw_result_metadata
|
206
|
-
@result_metadata = ResultMetadata.new(@raw_result_metadata)
|
207
|
-
end
|
208
|
-
end
|
209
|
-
hex_id = response.id.each_byte.map { |x| x.to_s(16).rjust(2, '0') }.join('')
|
210
|
-
@logger.debug('Statement %s prepared on node %s (%s:%d)' % [hex_id, connection[:host_id].to_s, connection.host, connection.port])
|
211
|
-
end
|
212
|
-
f.map(self)
|
213
|
-
end
|
214
|
-
|
215
|
-
# @private
|
216
|
-
def prepared?(connection)
|
217
|
-
!!connection[self]
|
218
|
-
end
|
219
|
-
|
220
|
-
# @private
|
221
|
-
def add_to_batch(batch, connection, bound_args)
|
222
|
-
statement_id = connection[self]
|
223
|
-
unless statement_id
|
224
|
-
raise ::ArgumentError, "Statement was not prepared"
|
225
|
-
end
|
226
|
-
unless bound_args.size == @raw_metadata.size
|
227
|
-
raise ::ArgumentError, "Expected #{@raw_metadata.size} arguments, got #{bound_args.size}"
|
228
|
-
end
|
229
|
-
batch.add_prepared(statement_id, @raw_metadata, bound_args)
|
230
|
-
end
|
231
|
-
|
232
|
-
private
|
233
|
-
|
234
|
-
def run(args, connection)
|
235
|
-
bound_args = args.shift(@raw_metadata.size)
|
236
|
-
unless bound_args.size == @raw_metadata.size && args.size <= 1
|
237
|
-
raise ::ArgumentError, "Expected #{@raw_metadata.size} arguments, got #{bound_args.size}"
|
238
|
-
end
|
239
|
-
options = @execute_options_decoder.decode_options(args.last)
|
240
|
-
statement_id = connection[self]
|
241
|
-
request_metadata = @raw_result_metadata.nil?
|
242
|
-
request = Protocol::ExecuteRequest.new(statement_id, @raw_metadata, bound_args, request_metadata, options[:consistency], options[:serial_consistency], options[:page_size], options[:paging_state], options[:trace])
|
243
|
-
f = @request_runner.execute(connection, request, options[:timeout], @raw_result_metadata)
|
244
|
-
if options.include?(:page_size)
|
245
|
-
f = f.map { |result| AsynchronousPreparedPagedQueryResult.new(self, request, result, options) }
|
246
|
-
end
|
247
|
-
f
|
248
|
-
end
|
249
|
-
end
|
250
|
-
|
251
|
-
# @private
|
252
|
-
class SynchronousPreparedStatement < PreparedStatement
|
253
|
-
include SynchronousBacktrace
|
254
|
-
|
255
|
-
def initialize(async_statement)
|
256
|
-
@async_statement = async_statement
|
257
|
-
@metadata = async_statement.metadata
|
258
|
-
@result_metadata = async_statement.result_metadata
|
259
|
-
end
|
260
|
-
|
261
|
-
def execute(*args)
|
262
|
-
synchronous_backtrace do
|
263
|
-
result = @async_statement.execute(*args).value
|
264
|
-
result = SynchronousPagedQueryResult.new(result) if result.is_a?(PagedQueryResult)
|
265
|
-
result
|
266
|
-
end
|
267
|
-
end
|
268
|
-
|
269
|
-
def batch(type=:logged, options=nil, &block)
|
270
|
-
if block_given?
|
271
|
-
synchronous_backtrace { @async_statement.batch(type, options, &block).value }
|
272
|
-
else
|
273
|
-
SynchronousPreparedStatementBatch.new(@async_statement.batch(type, options))
|
274
|
-
end
|
275
|
-
end
|
276
|
-
|
277
|
-
def pipeline
|
278
|
-
pl = Pipeline.new(@async_statement)
|
279
|
-
yield pl
|
280
|
-
synchronous_backtrace { pl.value }
|
281
|
-
end
|
282
|
-
|
283
|
-
def async
|
284
|
-
@async_statement
|
285
|
-
end
|
286
|
-
|
287
|
-
# @private
|
288
|
-
def prepared?(connection)
|
289
|
-
@async_statement.prepared?(connection)
|
290
|
-
end
|
291
|
-
|
292
|
-
# @private
|
293
|
-
def add_to_batch(batch, connection, bound_arguments)
|
294
|
-
@async_statement.add_to_batch(batch, connection, bound_arguments)
|
295
|
-
end
|
296
|
-
end
|
297
|
-
|
298
|
-
# @private
|
299
|
-
class Pipeline
|
300
|
-
def initialize(async_statement)
|
301
|
-
@async_statement = async_statement
|
302
|
-
@futures = []
|
303
|
-
end
|
304
|
-
|
305
|
-
def execute(*args)
|
306
|
-
@futures << @async_statement.execute(*args)
|
307
|
-
end
|
308
|
-
|
309
|
-
def value
|
310
|
-
Ione::Future.all(*@futures).value
|
311
|
-
end
|
312
|
-
end
|
313
|
-
end
|
314
|
-
end
|