cassandra-driver 0.1.0.alpha1 → 1.0.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. data/.yardopts +4 -0
  2. data/README.md +117 -0
  3. data/lib/cassandra.rb +320 -0
  4. data/lib/cassandra/auth.rb +97 -0
  5. data/lib/cassandra/auth/providers.rb +16 -0
  6. data/lib/cassandra/auth/providers/password.rb +73 -0
  7. data/lib/cassandra/client.rb +144 -0
  8. data/lib/cassandra/client/batch.rb +212 -0
  9. data/lib/cassandra/client/client.rb +591 -0
  10. data/lib/cassandra/client/column_metadata.rb +54 -0
  11. data/lib/cassandra/client/connection_manager.rb +72 -0
  12. data/lib/cassandra/client/connector.rb +272 -0
  13. data/lib/cassandra/client/execute_options_decoder.rb +59 -0
  14. data/lib/cassandra/client/null_logger.rb +37 -0
  15. data/lib/cassandra/client/peer_discovery.rb +50 -0
  16. data/lib/cassandra/client/prepared_statement.rb +314 -0
  17. data/lib/cassandra/client/query_result.rb +230 -0
  18. data/lib/cassandra/client/request_runner.rb +71 -0
  19. data/lib/cassandra/client/result_metadata.rb +48 -0
  20. data/lib/cassandra/client/void_result.rb +78 -0
  21. data/lib/cassandra/cluster.rb +191 -0
  22. data/lib/cassandra/cluster/client.rb +767 -0
  23. data/lib/cassandra/cluster/connector.rb +231 -0
  24. data/lib/cassandra/cluster/control_connection.rb +420 -0
  25. data/lib/cassandra/cluster/options.rb +40 -0
  26. data/lib/cassandra/cluster/registry.rb +181 -0
  27. data/lib/cassandra/cluster/schema.rb +321 -0
  28. data/lib/cassandra/cluster/schema/type_parser.rb +138 -0
  29. data/lib/cassandra/column.rb +92 -0
  30. data/lib/cassandra/compression.rb +66 -0
  31. data/lib/cassandra/compression/compressors/lz4.rb +72 -0
  32. data/lib/cassandra/compression/compressors/snappy.rb +66 -0
  33. data/lib/cassandra/driver.rb +86 -0
  34. data/lib/cassandra/errors.rb +79 -0
  35. data/lib/cassandra/execution/info.rb +51 -0
  36. data/lib/cassandra/execution/options.rb +77 -0
  37. data/lib/cassandra/execution/trace.rb +152 -0
  38. data/lib/cassandra/future.rb +675 -0
  39. data/lib/cassandra/host.rb +75 -0
  40. data/lib/cassandra/keyspace.rb +120 -0
  41. data/lib/cassandra/listener.rb +87 -0
  42. data/lib/cassandra/load_balancing.rb +112 -0
  43. data/lib/cassandra/load_balancing/policies.rb +18 -0
  44. data/lib/cassandra/load_balancing/policies/dc_aware_round_robin.rb +149 -0
  45. data/lib/cassandra/load_balancing/policies/round_robin.rb +95 -0
  46. data/lib/cassandra/load_balancing/policies/white_list.rb +90 -0
  47. data/lib/cassandra/protocol.rb +93 -0
  48. data/lib/cassandra/protocol/cql_byte_buffer.rb +307 -0
  49. data/lib/cassandra/protocol/cql_protocol_handler.rb +323 -0
  50. data/lib/cassandra/protocol/frame_decoder.rb +128 -0
  51. data/lib/cassandra/protocol/frame_encoder.rb +48 -0
  52. data/lib/cassandra/protocol/request.rb +38 -0
  53. data/lib/cassandra/protocol/requests/auth_response_request.rb +47 -0
  54. data/lib/cassandra/protocol/requests/batch_request.rb +76 -0
  55. data/lib/cassandra/protocol/requests/credentials_request.rb +47 -0
  56. data/lib/cassandra/protocol/requests/execute_request.rb +103 -0
  57. data/lib/cassandra/protocol/requests/options_request.rb +39 -0
  58. data/lib/cassandra/protocol/requests/prepare_request.rb +50 -0
  59. data/lib/cassandra/protocol/requests/query_request.rb +153 -0
  60. data/lib/cassandra/protocol/requests/register_request.rb +38 -0
  61. data/lib/cassandra/protocol/requests/startup_request.rb +49 -0
  62. data/lib/cassandra/protocol/requests/void_query_request.rb +24 -0
  63. data/lib/cassandra/protocol/response.rb +38 -0
  64. data/lib/cassandra/protocol/responses/auth_challenge_response.rb +41 -0
  65. data/lib/cassandra/protocol/responses/auth_success_response.rb +41 -0
  66. data/lib/cassandra/protocol/responses/authenticate_response.rb +41 -0
  67. data/lib/cassandra/protocol/responses/detailed_error_response.rb +60 -0
  68. data/lib/cassandra/protocol/responses/error_response.rb +50 -0
  69. data/lib/cassandra/protocol/responses/event_response.rb +39 -0
  70. data/lib/cassandra/protocol/responses/prepared_result_response.rb +64 -0
  71. data/lib/cassandra/protocol/responses/raw_rows_result_response.rb +43 -0
  72. data/lib/cassandra/protocol/responses/ready_response.rb +44 -0
  73. data/lib/cassandra/protocol/responses/result_response.rb +48 -0
  74. data/lib/cassandra/protocol/responses/rows_result_response.rb +139 -0
  75. data/lib/cassandra/protocol/responses/schema_change_event_response.rb +60 -0
  76. data/lib/cassandra/protocol/responses/schema_change_result_response.rb +57 -0
  77. data/lib/cassandra/protocol/responses/set_keyspace_result_response.rb +42 -0
  78. data/lib/cassandra/protocol/responses/status_change_event_response.rb +44 -0
  79. data/lib/cassandra/protocol/responses/supported_response.rb +41 -0
  80. data/lib/cassandra/protocol/responses/topology_change_event_response.rb +34 -0
  81. data/lib/cassandra/protocol/responses/void_result_response.rb +39 -0
  82. data/lib/cassandra/protocol/type_converter.rb +384 -0
  83. data/lib/cassandra/reconnection.rb +49 -0
  84. data/lib/cassandra/reconnection/policies.rb +20 -0
  85. data/lib/cassandra/reconnection/policies/constant.rb +48 -0
  86. data/lib/cassandra/reconnection/policies/exponential.rb +79 -0
  87. data/lib/cassandra/result.rb +215 -0
  88. data/lib/cassandra/retry.rb +142 -0
  89. data/lib/cassandra/retry/policies.rb +21 -0
  90. data/lib/cassandra/retry/policies/default.rb +47 -0
  91. data/lib/cassandra/retry/policies/downgrading_consistency.rb +71 -0
  92. data/lib/cassandra/retry/policies/fallthrough.rb +39 -0
  93. data/lib/cassandra/session.rb +195 -0
  94. data/lib/cassandra/statement.rb +22 -0
  95. data/lib/cassandra/statements.rb +23 -0
  96. data/lib/cassandra/statements/batch.rb +95 -0
  97. data/lib/cassandra/statements/bound.rb +46 -0
  98. data/lib/cassandra/statements/prepared.rb +59 -0
  99. data/lib/cassandra/statements/simple.rb +58 -0
  100. data/lib/cassandra/statements/void.rb +33 -0
  101. data/lib/cassandra/table.rb +254 -0
  102. data/lib/cassandra/time_uuid.rb +141 -0
  103. data/lib/cassandra/util.rb +169 -0
  104. data/lib/cassandra/uuid.rb +104 -0
  105. data/lib/cassandra/version.rb +17 -1
  106. metadata +134 -8
@@ -0,0 +1,71 @@
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 RequestRunner
23
+ def execute(connection, request, timeout=nil, raw_metadata=nil)
24
+ connection.send_request(request, timeout).map do |response|
25
+ case response
26
+ when Protocol::RawRowsResultResponse
27
+ LazyQueryResult.new(raw_metadata, response, response.trace_id, response.paging_state)
28
+ when Protocol::RowsResultResponse
29
+ QueryResult.new(response.metadata, response.rows, response.trace_id, response.paging_state)
30
+ when Protocol::VoidResultResponse
31
+ response.trace_id ? VoidResult.new(response.trace_id) : VoidResult::INSTANCE
32
+ when Protocol::ErrorResponse
33
+ cql = request.is_a?(Protocol::QueryRequest) ? request.cql : nil
34
+ details = response.respond_to?(:details) ? response.details : nil
35
+ raise Errors::QueryError.new(response.code, response.message, cql, details)
36
+ when Protocol::SetKeyspaceResultResponse
37
+ KeyspaceChanged.new(response.keyspace)
38
+ when Protocol::AuthenticateResponse
39
+ AuthenticationRequired.new(response.authentication_class)
40
+ when Protocol::SupportedResponse
41
+ response.options
42
+ else
43
+ if block_given?
44
+ yield response
45
+ else
46
+ nil
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ # @private
54
+ class AuthenticationRequired
55
+ attr_reader :authentication_class
56
+
57
+ def initialize(authentication_class)
58
+ @authentication_class = authentication_class
59
+ end
60
+ end
61
+
62
+ # @private
63
+ class KeyspaceChanged
64
+ attr_reader :keyspace
65
+
66
+ def initialize(keyspace)
67
+ @keyspace = keyspace
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,48 @@
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 collection of metadata (keyspace, table, name and type) of a result set.
22
+ #
23
+ # @see Cassandra::Client::ColumnMetadata
24
+ class ResultMetadata
25
+ include Enumerable
26
+
27
+ # @private
28
+ def initialize(metadata)
29
+ @metadata = metadata.each_with_object({}) { |m, h| h[m[2]] = ColumnMetadata.new(*m) }
30
+ end
31
+
32
+ # Returns the column metadata
33
+ #
34
+ # @return [ColumnMetadata] column_metadata the metadata for the column
35
+ def [](column_name)
36
+ @metadata[column_name]
37
+ end
38
+
39
+ # Iterates over the metadata for each column
40
+ #
41
+ # @yieldparam [ColumnMetadata] metadata the metadata for each column
42
+ # @return [Enumerable<ColumnMetadata>]
43
+ def each(&block)
44
+ @metadata.each_value(&block)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,78 @@
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
+ # Many CQL queries do not return any rows, but they can still return
22
+ # data about the query, for example the trace ID. This class exist to make
23
+ # that data available.
24
+ #
25
+ # It has the exact same API as {Cassandra::Client::QueryResult} so that you don't
26
+ # need to check the return value of for example {Cassandra::Client::Client#execute}.
27
+ #
28
+ # @see Cassandra::Client::QueryResult
29
+ # @see Cassandra::Client::Client#execute
30
+ # @see Cassandra::Client::PreparedStatement#execute
31
+ class VoidResult
32
+ include Enumerable
33
+
34
+ INSTANCE = self.new
35
+
36
+ # @return [ResultMetadata]
37
+ attr_reader :metadata
38
+
39
+ # The ID of the query trace associated with the query, if any.
40
+ #
41
+ # @return [Cassandra::Uuid]
42
+ attr_reader :trace_id
43
+
44
+ # @private
45
+ def initialize(trace_id=nil)
46
+ @trace_id = trace_id
47
+ @metadata = EMPTY_METADATA
48
+ end
49
+
50
+ # Always returns true
51
+ def empty?
52
+ true
53
+ end
54
+
55
+ # Always returns true
56
+ def last_page?
57
+ true
58
+ end
59
+
60
+ # Always returns nil
61
+ def next_page
62
+ nil
63
+ end
64
+
65
+ # No-op for API compatibility with {QueryResult}.
66
+ #
67
+ # @return [Enumerable]
68
+ def each(&block)
69
+ self
70
+ end
71
+ alias_method :each_row, :each
72
+
73
+ private
74
+
75
+ EMPTY_METADATA = ResultMetadata.new([])
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,191 @@
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
+ # Cluster represents a cassandra cluster. It serves as a {Cassandra::Session}
21
+ # factory and a collection of metadata.
22
+ #
23
+ # @see Cassandra::Cluster#connect Creating a new session
24
+ # @see Cassandra::Cluster#each_host Getting all peers in the cluster
25
+ # @see Cassandra::Cluster#each_keyspace Getting all existing keyspaces
26
+ class Cluster
27
+ extend Forwardable
28
+
29
+ # @!method each_host
30
+ # Yield or enumerate each member of this cluster
31
+ # @overload each_host
32
+ # @yieldparam host [Cassandra::Host] current host
33
+ # @return [Array<Cassandra::Host>] a list of hosts
34
+ # @overload each_host
35
+ # @return [Enumerator<Cassandra::Host>] an enumerator
36
+ # @!parse alias :hosts :each_host
37
+ #
38
+ # @!method host(address)
39
+ # Find a host by its address
40
+ # @param address [IPAddr, String] ip address
41
+ # @return [Cassandra::Host, nil] host or nil
42
+ #
43
+ # @!method has_host?(address)
44
+ # Determine if a host by a given address exists
45
+ # @param address [IPAddr, String] ip address
46
+ # @return [Boolean] true or false
47
+ def_delegators :@registry, :hosts, :each_host, :host, :has_host?
48
+
49
+ # @!method each_keyspace
50
+ # Yield or enumerate each keyspace defined in this cluster
51
+ # @overload each_keyspace
52
+ # @yieldparam keyspace [Cassandra::Keyspace] current keyspace
53
+ # @return [Array<Cassandra::Keyspace>] a list of keyspaces
54
+ # @overload each_keyspace
55
+ # @return [Enumerator<Cassandra::Keyspace>] an enumerator
56
+ # @!parse alias :keyspaces :each_keyspace
57
+ #
58
+ # @!method keyspace(name)
59
+ # Find a keyspace by name
60
+ # @param name [String] keyspace name
61
+ # @return [Cassandra::Keyspace, nil] keyspace or nil
62
+ #
63
+ # @!method has_keyspace?(name)
64
+ # Determine if a keyspace by a given name exists
65
+ # @param name [String] keyspace name
66
+ # @return [Boolean] true or false
67
+ def_delegators :@schema, :keyspaces, :each_keyspace, :keyspace, :has_keyspace?
68
+
69
+ # @private
70
+ def initialize(logger, io_reactor, control_connection, cluster_registry, cluster_schema, execution_options, connection_options, load_balancing_policy, reconnection_policy, retry_policy, connector, futures_factory)
71
+ @logger = logger
72
+ @io_reactor = io_reactor
73
+ @control_connection = control_connection
74
+ @registry = cluster_registry
75
+ @schema = cluster_schema
76
+ @execution_options = execution_options
77
+ @connection_options = connection_options
78
+ @load_balancing_policy = load_balancing_policy
79
+ @reconnection_policy = reconnection_policy
80
+ @retry_policy = retry_policy
81
+ @connector = connector
82
+ @futures = futures_factory
83
+ end
84
+
85
+ # Register a cluster state listener. State listener will start receiving
86
+ # notifications about topology and schema changes
87
+ #
88
+ # @param listener [Cassandra::Listener] cluster state listener
89
+ # @return [self]
90
+ def register(listener)
91
+ @registry.add_listener(listener)
92
+ @schema.add_listener(listener)
93
+ self
94
+ end
95
+
96
+ # Unregister a cluster state listener. State listener will stop receiving
97
+ # notifications about topology and schema changes
98
+ #
99
+ # @param listener [Cassandra::Listener] cluster state listener
100
+ # @return [self]
101
+ def unregister(listener)
102
+ @registry.remove_listener(listener)
103
+ @schema.remove_listener(listener)
104
+ self
105
+ end
106
+
107
+ # Asynchronously create a new session, optionally scoped to a keyspace
108
+ #
109
+ # @param keyspace [String] optional keyspace to scope session to
110
+ #
111
+ # @return [Cassandra::Future<Cassandra::Session>] a future new session that
112
+ # can prepare and execute statements
113
+ def connect_async(keyspace = nil)
114
+ if !keyspace.nil? && !keyspace.is_a?(::String)
115
+ return @futures.error(::ArgumentError.new("keyspace must be a string, #{keyspace.inspect} given"))
116
+ end
117
+
118
+ client = Client.new(@logger, @registry, @io_reactor, @connector, @load_balancing_policy, @reconnection_policy, @retry_policy, @connection_options, @futures)
119
+ session = Session.new(client, @execution_options, @futures)
120
+ promise = @futures.promise
121
+
122
+ client.connect.on_complete do |f|
123
+ if f.resolved?
124
+ if keyspace
125
+ f = session.execute_async("USE #{keyspace}")
126
+
127
+ f.on_success {promise.fulfill(session)}
128
+ f.on_failure {|e| promise.break(e)}
129
+ else
130
+ promise.fulfill(session)
131
+ end
132
+ else
133
+ f.on_failure {|e| promise.break(e)}
134
+ end
135
+ end
136
+
137
+ promise.future
138
+ end
139
+
140
+ # Synchronously create a new session, optionally scoped to a keyspace
141
+ #
142
+ # @param keyspace [String] optional keyspace to scope session to
143
+ # @raise [ArgumentError] if keyspace is not a String
144
+ # @return [Cassandra::Session] a new session that can prepare and execute
145
+ # statements
146
+ #
147
+ # @see Cassandra::Cluster#connect_async
148
+ def connect(keyspace = nil)
149
+ connect_async(keyspace).get
150
+ end
151
+
152
+ # Asynchronously closes all sessions managed by this cluster
153
+ #
154
+ # @return [Cassandra::Future<Cassandra::Cluster>] a future that resolves to
155
+ # self once closed
156
+ def close_async
157
+ promise = @futures.promise
158
+
159
+ @control_connection.close_async.on_complete do |f|
160
+ if f.resolved?
161
+ promise.fulfill(self)
162
+ else
163
+ f.on_failure {|e| promise.break(e)}
164
+ end
165
+ end
166
+
167
+ promise.future
168
+ end
169
+
170
+ # Synchronously closes all sessions managed by this cluster
171
+ #
172
+ # @return [self] this cluster
173
+ #
174
+ # @see Cassandra::Cluster#close_async
175
+ def close
176
+ close_async.get
177
+ end
178
+
179
+ # @return [String] a CLI-friendly cluster representation
180
+ def inspect
181
+ "#<#{self.class.name}:0x#{self.object_id.to_s(16)}>"
182
+ end
183
+ end
184
+ end
185
+
186
+ require 'cassandra/cluster/client'
187
+ require 'cassandra/cluster/connector'
188
+ require 'cassandra/cluster/control_connection'
189
+ require 'cassandra/cluster/options'
190
+ require 'cassandra/cluster/registry'
191
+ require 'cassandra/cluster/schema'
@@ -0,0 +1,767 @@
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
+ class Cluster
21
+ # @private
22
+ class Client
23
+ include MonitorMixin
24
+
25
+ def initialize(logger, cluster_registry, io_reactor, connector, load_balancing_policy, reconnection_policy, retry_policy, connection_options, futures_factory)
26
+ @logger = logger
27
+ @registry = cluster_registry
28
+ @reactor = io_reactor
29
+ @connector = connector
30
+ @load_balancing_policy = load_balancing_policy
31
+ @reconnection_policy = reconnection_policy
32
+ @retry_policy = retry_policy
33
+ @connection_options = connection_options
34
+ @futures = futures_factory
35
+ @connecting_hosts = ::Set.new
36
+ @connections = ::Hash.new
37
+ @prepared_statements = ::Hash.new
38
+ @preparing_statements = ::Hash.new
39
+ @keyspace = nil
40
+ @state = :idle
41
+
42
+ mon_initialize
43
+ end
44
+
45
+ def connect
46
+ synchronize do
47
+ return CLIENT_CLOSED if @state == :closed || @state == :closing
48
+ return @connected_future if @state == :connecting || @state == :connected
49
+
50
+ @state = :connecting
51
+ @connecting_hosts.merge(@registry.hosts)
52
+ end
53
+
54
+ @connected_future = begin
55
+ @registry.add_listener(self)
56
+
57
+ futures = @connecting_hosts.map do |host|
58
+ f = connect_to_host_maybe_retry(host, @load_balancing_policy.distance(host))
59
+ f.recover do |error|
60
+ Cassandra::Client::FailedConnection.new(error, host)
61
+ end
62
+ end
63
+
64
+ Ione::Future.all(*futures).map do |connections|
65
+ connections.flatten!
66
+ raise NO_HOSTS if connections.empty?
67
+
68
+ unless connections.any?(&:connected?)
69
+ errors = {}
70
+ connections.each {|c| errors[c.host] = c.error}
71
+ raise Errors::NoHostsAvailable.new(errors)
72
+ end
73
+
74
+ self
75
+ end
76
+ end
77
+ @connected_future.on_complete(&method(:connected))
78
+ @connected_future
79
+ end
80
+
81
+ def close
82
+ state = nil
83
+
84
+ synchronize do
85
+ return CLIENT_NOT_CONNECTED if @state == :idle
86
+ return @closed_future if @state == :closed || @state == :closing
87
+
88
+ state, @state = @state, :closing
89
+ end
90
+
91
+ @closed_future = begin
92
+ @registry.remove_listener(self)
93
+
94
+ if state == :connecting
95
+ f = @connected_future.recover.flat_map { close_connections }
96
+ else
97
+ f = close_connections
98
+ end
99
+
100
+ f.map(self)
101
+ end
102
+ @closed_future.on_complete(&method(:closed))
103
+ @closed_future
104
+ end
105
+
106
+ # These methods shall be called from inside reactor thread only
107
+ def host_found(host)
108
+ nil
109
+ end
110
+
111
+ def host_lost(host)
112
+ nil
113
+ end
114
+
115
+ def host_up(host)
116
+ synchronize do
117
+ return Ione::Future.resolved if @connecting_hosts.include?(host)
118
+ @connecting_hosts << host
119
+ end
120
+
121
+ connect_to_host_maybe_retry(host, @load_balancing_policy.distance(host)).map(nil)
122
+ end
123
+
124
+ def host_down(host)
125
+ manager = nil
126
+
127
+ synchronize do
128
+ return Ione::Future.resolved if !@connections.has_key?(host) && !@connecting_hosts.include?(host)
129
+
130
+ @logger.info("Session disconnecting from ip=#{host.ip}")
131
+ @connecting_hosts.delete(host)
132
+ @prepared_statements.delete(host)
133
+ @preparing_statements.delete(host)
134
+
135
+ manager = @connections.delete(host)
136
+ end
137
+
138
+ if manager
139
+ Ione::Future.all(*manager.snapshot.map! {|c| c.close}).map(nil)
140
+ else
141
+ Ione::Future.resolved
142
+ end
143
+ end
144
+
145
+ def query(statement, options, paging_state = nil)
146
+ request = Protocol::QueryRequest.new(statement.cql, statement.params, nil, options.consistency, options.serial_consistency, options.page_size, paging_state, options.trace?)
147
+ timeout = options.timeout
148
+ promise = @futures.promise
149
+
150
+ keyspace = @keyspace
151
+ plan = @load_balancing_policy.plan(keyspace, statement, options)
152
+
153
+ send_request_by_plan(promise, keyspace, statement, options, request, plan, timeout)
154
+
155
+ promise.future
156
+ end
157
+
158
+ def prepare(cql, options)
159
+ request = Protocol::PrepareRequest.new(cql, options.trace?)
160
+ timeout = options.timeout
161
+ promise = @futures.promise
162
+
163
+ keyspace = @keyspace
164
+ statement = VOID_STATEMENT
165
+ plan = @load_balancing_policy.plan(keyspace, statement, options)
166
+
167
+ send_request_by_plan(promise, keyspace, statement, options, request, plan, timeout)
168
+
169
+ promise.future
170
+ end
171
+
172
+ def execute(statement, options, paging_state = nil)
173
+ timeout = options.timeout
174
+ result_metadata = statement.result_metadata
175
+ request = Protocol::ExecuteRequest.new(nil, statement.params_metadata, statement.params, result_metadata.nil?, options.consistency, options.serial_consistency, options.page_size, paging_state, options.trace?)
176
+ promise = @futures.promise
177
+
178
+ keyspace = @keyspace
179
+ plan = @load_balancing_policy.plan(keyspace, statement, options)
180
+
181
+ execute_by_plan(promise, keyspace, statement, options, request, plan, timeout)
182
+
183
+ promise.future
184
+ end
185
+
186
+ def batch(statement, options)
187
+ timeout = options.timeout
188
+ keyspace = @keyspace
189
+ plan = @load_balancing_policy.plan(keyspace, statement, options)
190
+ promise = @futures.promise
191
+
192
+ batch_by_plan(promise, keyspace, statement, options, plan, timeout)
193
+
194
+ promise.future
195
+ end
196
+
197
+ def inspect
198
+ "#<#{self.class.name}:0x#{self.object_id.to_s(16)}>"
199
+ end
200
+
201
+ private
202
+
203
+ NO_CONNECTIONS = Ione::Future.resolved([])
204
+ BATCH_TYPES = {
205
+ :logged => Protocol::BatchRequest::LOGGED_TYPE,
206
+ :unlogged => Protocol::BatchRequest::UNLOGGED_TYPE,
207
+ :counter => Protocol::BatchRequest::COUNTER_TYPE,
208
+ }.freeze
209
+ CLIENT_CLOSED = Ione::Future.failed(Errors::ClientError.new('Cannot connect a closed client'))
210
+ CLIENT_NOT_CONNECTED = Ione::Future.failed(Errors::ClientError.new('Cannot close a not connected client'))
211
+ NOT_CONNECTED = Errors::NotConnectedError.new
212
+
213
+ UNAVAILABLE_ERROR_CODE = 0x1000
214
+ WRITE_TIMEOUT_ERROR_CODE = 0x1100
215
+ READ_TIMEOUT_ERROR_CODE = 0x1200
216
+
217
+ SELECT_SCHEMA_PEERS = Protocol::QueryRequest.new("SELECT peer, rpc_address, schema_version FROM system.peers", nil, nil, :one)
218
+ SELECT_SCHEMA_LOCAL = Protocol::QueryRequest.new("SELECT schema_version FROM system.local WHERE key='local'", nil, nil, :one)
219
+
220
+ def connected(f)
221
+ if f.resolved?
222
+ synchronize do
223
+ @state = :connected
224
+ end
225
+
226
+ @logger.info('Session connected')
227
+ else
228
+ synchronize do
229
+ @state = :defunct
230
+ end
231
+
232
+ f.on_failure do |e|
233
+ @logger.error('Session connect failed: %s' % e.message)
234
+ end
235
+
236
+ close
237
+ end
238
+ end
239
+
240
+ def closed(f)
241
+ synchronize do
242
+ @state = :closed
243
+
244
+ if f.resolved?
245
+ @logger.info('Session closed')
246
+ else
247
+ f.on_failure do |e|
248
+ @logger.error('Session close failed: %s' % e.message)
249
+ end
250
+ end
251
+ end
252
+ end
253
+
254
+ def close_connections
255
+ @logger.info('Session closing')
256
+
257
+ futures = []
258
+ synchronize do
259
+ @connections.each do |host, connections|
260
+ connections.snapshot.each do |c|
261
+ @logger.info("Disconnecting ip=#{c.host}")
262
+ futures << c.close
263
+ end
264
+ end.clear
265
+ end
266
+
267
+ Ione::Future.all(*futures).map(self)
268
+ end
269
+
270
+ def connect_to_host_maybe_retry(host, distance)
271
+ f = connect_to_host(host, distance)
272
+
273
+ f.on_failure do |e|
274
+ connect_to_host_with_retry(host, @reconnection_policy.schedule) if e.is_a?(Io::ConnectionError) || e.is_a?(::SystemCallError) || e.is_a?(::SocketError)
275
+ end
276
+
277
+ f
278
+ end
279
+
280
+ def connect_to_host_with_retry(host, schedule)
281
+ interval = schedule.next
282
+
283
+ @logger.info("Session started reconnecting to ip=#{host.ip} delay=#{interval}")
284
+
285
+ f = @reactor.schedule_timer(interval)
286
+ f.flat_map do
287
+ if synchronize { @connecting_hosts.include?(host) }
288
+ connect_to_host(host, @load_balancing_policy.distance(host)).fallback do |e|
289
+ if e.is_a?(Io::ConnectionError) || e.is_a?(::SystemCallError) || e.is_a?(::SocketError)
290
+ connect_to_host_with_retry(host, schedule)
291
+ else
292
+ Ione::Future.failed(e)
293
+ end
294
+ end
295
+ else
296
+ @logger.info("Session reconnection to ip=#{host.ip} cancelled")
297
+
298
+ NO_CONNECTIONS
299
+ end
300
+ end
301
+ end
302
+
303
+ def connect_to_host(host, distance)
304
+ case distance
305
+ when :ignore
306
+ return NO_CONNECTIONS
307
+ when :local
308
+ pool_size = @connection_options.connections_per_local_node
309
+ when :remote
310
+ pool_size = @connection_options.connections_per_remote_node
311
+ else
312
+ raise ::ArgumentError, "distance must be one of :ignore, :local or :remote, #{distance.inspect} given"
313
+ end
314
+
315
+ @logger.info("Session connecting to ip=#{host.ip}")
316
+
317
+ f = @connector.connect_many(host, pool_size)
318
+
319
+ f.on_value do |connections|
320
+ manager = nil
321
+
322
+ synchronize do
323
+ @logger.info("Session connected to ip=#{host.ip}")
324
+ @connecting_hosts.delete(host)
325
+ @prepared_statements[host] = {}
326
+ @preparing_statements[host] = {}
327
+ manager = @connections[host] ||= Cassandra::Client::ConnectionManager.new
328
+ end
329
+
330
+ manager.add_connections(connections)
331
+ end
332
+
333
+ f
334
+ end
335
+
336
+ def execute_by_plan(promise, keyspace, statement, options, request, plan, timeout, errors = nil, hosts = [])
337
+ unless plan.has_next?
338
+ promise.break(Errors::NoHostsAvailable.new(errors || {}))
339
+ return
340
+ end
341
+
342
+ hosts << host = plan.next
343
+ manager = nil
344
+ synchronize { manager = @connections[host] }
345
+
346
+ unless manager
347
+ errors ||= {}
348
+ errors[host] = NOT_CONNECTED
349
+ return execute_by_plan(promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
350
+ end
351
+
352
+ connection = manager.random_connection
353
+
354
+ if keyspace && connection.keyspace != keyspace
355
+ switch = switch_keyspace(connection, keyspace, timeout)
356
+ switch.on_complete do |s|
357
+ if s.resolved?
358
+ prepare_and_send_request_by_plan(host, connection, promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
359
+ else
360
+ s.on_failure do |e|
361
+ if e.is_a?(Errors::QueryError)
362
+ promise.break(e)
363
+ else
364
+ errors ||= {}
365
+ errors[host] = e
366
+ execute_by_plan(promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
367
+ end
368
+ end
369
+ end
370
+ end
371
+ else
372
+ prepare_and_send_request_by_plan(host, connection, promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
373
+ end
374
+ rescue => e
375
+ errors ||= {}
376
+ errors[host] = e
377
+ execute_by_plan(promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
378
+ end
379
+
380
+ def prepare_and_send_request_by_plan(host, connection, promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
381
+ cql = statement.cql
382
+ id = synchronize { @prepared_statements[host][cql] }
383
+
384
+ if id
385
+ request.id = id
386
+ do_send_request_by_plan(host, connection, promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
387
+ else
388
+ prepare = prepare_statement(host, connection, cql, timeout)
389
+ prepare.on_complete do |_|
390
+ if prepare.resolved?
391
+ request.id = prepare.value
392
+ do_send_request_by_plan(host, connection, promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
393
+ else
394
+ prepare.on_failure do |e|
395
+ if e.is_a?(Errors::QueryError)
396
+ promise.break(e)
397
+ else
398
+ errors ||= {}
399
+ errors[host] = e
400
+ execute_by_plan(promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
401
+ end
402
+ end
403
+ end
404
+ end
405
+ end
406
+ end
407
+
408
+ def batch_by_plan(promise, keyspace, statement, options, plan, timeout, errors = nil, hosts = [])
409
+ unless plan.has_next?
410
+ promise.break(Errors::NoHostsAvailable.new(errors || {}))
411
+ return
412
+ end
413
+
414
+ hosts << host = plan.next
415
+ manager = nil
416
+ synchronize { manager = @connections[host] }
417
+
418
+ unless manager
419
+ errors ||= {}
420
+ errors[host] = NOT_CONNECTED
421
+ return batch_by_plan(promise, keyspace, statement, options, plan, timeout, errors, hosts)
422
+ end
423
+
424
+ connection = manager.random_connection
425
+
426
+ if keyspace && connection.keyspace != keyspace
427
+ switch = switch_keyspace(connection, keyspace, timeout)
428
+ switch.on_complete do |s|
429
+ if s.resolved?
430
+ batch_and_send_request_by_plan(host, connection, promise, keyspace, statement, options, plan, timeout, errors, hosts)
431
+ else
432
+ s.on_failure do |e|
433
+ if e.is_a?(Errors::QueryError)
434
+ promise.break(e)
435
+ else
436
+ errors ||= {}
437
+ errors[host] = e
438
+ batch_by_plan(promise, keyspace, statement, options, plan, timeout, errors, hosts)
439
+ end
440
+ end
441
+ end
442
+ end
443
+ else
444
+ batch_and_send_request_by_plan(host, connection, promise, keyspace, statement, options, plan, timeout, errors, hosts)
445
+ end
446
+ rescue => e
447
+ errors ||= {}
448
+ errors[host] = e
449
+ batch_by_plan(promise, keyspace, statement, options, plan, timeout, errors, hosts)
450
+ end
451
+
452
+ def batch_and_send_request_by_plan(host, connection, promise, keyspace, statement, options, plan, timeout, errors, hosts)
453
+ request = Protocol::BatchRequest.new(BATCH_TYPES[statement.type], options.consistency, options.trace?)
454
+ unprepared = Hash.new {|hash, cql| hash[cql] = []}
455
+
456
+ statement.statements.each do |statement|
457
+ cql = statement.cql
458
+
459
+ if statement.is_a?(Statements::Bound)
460
+ id = synchronize { @prepared_statements[host][cql] }
461
+
462
+ if id
463
+ request.add_prepared(id, statement.params_metadata, statement.params)
464
+ else
465
+ unprepared[cql] << statement
466
+ end
467
+ else
468
+ request.add_query(cql, statement.params)
469
+ end
470
+ end
471
+
472
+ if unprepared.empty?
473
+ do_send_request_by_plan(host, connection, promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
474
+ else
475
+ to_prepare = unprepared.to_a
476
+ futures = to_prepare.map do |cql, _|
477
+ prepare_statement(host, connection, cql, timeout)
478
+ end
479
+
480
+ Ione::Future.all(*futures).on_complete do |f|
481
+ if f.resolved?
482
+ prepared_ids = f.value
483
+ to_prepare.each_with_index do |(_, statements), i|
484
+ statements.each do |statement|
485
+ request.add_prepared(prepared_ids[i], statement.params_metadata, statement.params)
486
+ end
487
+ end
488
+
489
+ do_send_request_by_plan(host, connection, promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
490
+ else
491
+ f.on_failure do |e|
492
+ if e.is_a?(Errors::QueryError)
493
+ promise.break(e)
494
+ else
495
+ errors ||= {}
496
+ errors[host] = e
497
+ batch_by_plan(promise, keyspace, statement, options, plan, timeout, errors, hosts)
498
+ end
499
+ end
500
+ end
501
+ end
502
+ end
503
+ end
504
+
505
+ def send_request_by_plan(promise, keyspace, statement, options, request, plan, timeout, errors = nil, hosts = [])
506
+ unless plan.has_next?
507
+ promise.break(Errors::NoHostsAvailable.new(errors || {}))
508
+ return
509
+ end
510
+
511
+ hosts << host = plan.next
512
+ manager = nil
513
+ synchronize { manager = @connections[host] }
514
+
515
+ unless manager
516
+ errors ||= {}
517
+ errors[host] = NOT_CONNECTED
518
+ return send_request_by_plan(promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
519
+ end
520
+
521
+ connection = manager.random_connection
522
+
523
+ if keyspace && connection.keyspace != keyspace
524
+ switch = switch_keyspace(connection, keyspace, timeout)
525
+ switch.on_complete do |s|
526
+ if s.resolved?
527
+ do_send_request_by_plan(host, connection, promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
528
+ else
529
+ s.on_failure do |e|
530
+ if e.is_a?(Errors::QueryError)
531
+ promise.break(e)
532
+ else
533
+ errors ||= {}
534
+ errors[host] = e
535
+ send_request_by_plan(promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
536
+ end
537
+ end
538
+ end
539
+ end
540
+ else
541
+ do_send_request_by_plan(host, connection, promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
542
+ end
543
+ rescue => e
544
+ errors ||= {}
545
+ errors[host] = e
546
+ send_request_by_plan(promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
547
+ end
548
+
549
+ def do_send_request_by_plan(host, connection, promise, keyspace, statement, options, request, plan, timeout, errors, hosts, retries = 0)
550
+ request.retries = retries
551
+
552
+ f = connection.send_request(request, timeout)
553
+ f.on_complete do |f|
554
+ if f.resolved?
555
+ r = f.value
556
+ case r
557
+ when Protocol::DetailedErrorResponse
558
+ details = r.details
559
+ decision = begin
560
+ case r.code
561
+ when UNAVAILABLE_ERROR_CODE
562
+ @retry_policy.unavailable(statement, details[:cl], details[:required], details[:alive], retries)
563
+ when WRITE_TIMEOUT_ERROR_CODE
564
+ details[:write_type] = write_type = details[:write_type].downcase!.to_sym
565
+ @retry_policy.write_timeout(statement, details[:cl], write_type, details[:blockfor], details[:received], retries)
566
+ when READ_TIMEOUT_ERROR_CODE
567
+ @retry_policy.read_timeout(statement, details[:cl], details[:blockfor], details[:received], details[:data_present], retries)
568
+ else
569
+ promise.break(Errors::QueryError.new(r.code, r.message, statement.cql, details))
570
+ break
571
+ end
572
+ rescue => e
573
+ promise.break(e)
574
+ break
575
+ end
576
+
577
+ case decision
578
+ when Retry::Decisions::Retry
579
+ request.consistency = decision.consistency
580
+ do_send_request_by_plan(host, connection, promise, keyspace, statement, options, request, plan, timeout, errors, hosts, retries + 1)
581
+ when Retry::Decisions::Ignore
582
+ promise.fulfill(Results::Void.new(r.trace_id, keyspace, statement, options, hosts, request.consistency, retries, self, @futures))
583
+ when Retry::Decisions::Reraise
584
+ promise.break(Errors::QueryError.new(r.code, r.message, statement.cql, r.details))
585
+ else
586
+ promise.break(Errors::QueryError.new(r.code, r.message, statement.cql, r.details))
587
+ end
588
+ when Protocol::ErrorResponse
589
+ promise.break(Errors::QueryError.new(r.code, r.message, statement.cql, nil))
590
+ when Protocol::SetKeyspaceResultResponse
591
+ @keyspace = r.keyspace
592
+ promise.fulfill(Results::Void.new(r.trace_id, keyspace, statement, options, hosts, request.consistency, retries, self, @futures))
593
+ when Protocol::PreparedResultResponse
594
+ cql = request.cql
595
+ synchronize do
596
+ @prepared_statements[host][cql] = r.id
597
+ @preparing_statements[host].delete(cql)
598
+ end
599
+
600
+ execution_info = create_execution_info(keyspace, statement, options, request, r, hosts)
601
+
602
+ promise.fulfill(Statements::Prepared.new(cql, r.metadata, r.result_metadata, execution_info))
603
+ when Protocol::RawRowsResultResponse
604
+ r.materialize(statement.result_metadata)
605
+ promise.fulfill(Results::Paged.new(r.rows, r.paging_state, r.trace_id, keyspace, statement, options, hosts, request.consistency, retries, self, @futures))
606
+ when Protocol::RowsResultResponse
607
+ promise.fulfill(Results::Paged.new(r.rows, r.paging_state, r.trace_id, keyspace, statement, options, hosts, request.consistency, retries, self, @futures))
608
+ when Protocol::SchemaChangeResultResponse
609
+ if r.change == 'DROPPED' && r.keyspace == @keyspace && r.table.empty?
610
+ @keyspace = nil
611
+ end
612
+
613
+ wait_for_schema_agreement(connection, @reconnection_policy.schedule).on_complete do |f|
614
+ unless f.resolved?
615
+ f.on_failure do |e|
616
+ @logger.error("Schema agreement error: #{e.class.name}: #{e.message}\n#{e.backtrace.join("\n")}")
617
+ end
618
+ end
619
+ promise.fulfill(Results::Void.new(r.trace_id, keyspace, statement, options, hosts, request.consistency, retries, self, @futures))
620
+ end
621
+ else
622
+ promise.fulfill(Results::Void.new(r.trace_id, keyspace, statement, options, hosts, request.consistency, retries, self, @futures))
623
+ end
624
+ else
625
+ f.on_failure do |e|
626
+ errors ||= {}
627
+ errors[host] = e
628
+ case request
629
+ when Protocol::QueryRequest, Protocol::PrepareRequest
630
+ send_request_by_plan(promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
631
+ when Protocol::ExecuteRequest
632
+ execute_by_plan(promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
633
+ when Protocol::BatchRequest
634
+ batch_by_plan(promise, keyspace, statement, options, plan, timeout, errors, hosts)
635
+ else
636
+ promise.break(e)
637
+ end
638
+ end
639
+ end
640
+ end
641
+ end
642
+
643
+ def wait_for_schema_agreement(connection, schedule)
644
+ @logger.info('Fetching schema versions')
645
+
646
+ peers = connection.send_request(SELECT_SCHEMA_PEERS)
647
+ local = connection.send_request(SELECT_SCHEMA_LOCAL)
648
+
649
+ Ione::Future.all(peers, local).flat_map do |(peers, local)|
650
+ @logger.info('Fetched schema versions')
651
+
652
+ peers = peers.rows
653
+ local = local.rows
654
+
655
+ versions = ::Set.new
656
+
657
+ unless local.empty?
658
+ host = @registry.host(connection.host)
659
+
660
+ if host && host.up?
661
+ versions << version = local.first['schema_version']
662
+ @logger.debug("Host #{host.ip} schema version: #{version}")
663
+ end
664
+ end
665
+
666
+ peers.each do |row|
667
+ host = @registry.host(peer_ip(row))
668
+ next unless host && host.up?
669
+
670
+ versions << version = row['schema_version']
671
+ @logger.debug("Host #{host.ip} schema version: #{version}")
672
+ end
673
+
674
+ if versions.one?
675
+ @logger.info('All hosts have the same schema version')
676
+ Ione::Future.resolved
677
+ else
678
+ interval = schedule.next
679
+ @logger.info("Hosts don't yet agree on schema: #{versions.to_a.inspect}, retrying in #{interval} seconds")
680
+ @reactor.schedule_timer(interval).flat_map do
681
+ wait_for_schema_agreement(connection, schedule)
682
+ end
683
+ end
684
+ end
685
+ end
686
+
687
+ def peer_ip(data)
688
+ ip = data['rpc_address']
689
+ ip = data['peer'] if ip == '0.0.0.0'
690
+ ip
691
+ end
692
+
693
+ def switch_keyspace(connection, keyspace, timeout)
694
+ pending_keyspace = connection[:pending_keyspace]
695
+ pending_switch = connection[:pending_switch]
696
+
697
+ return pending_switch || Ione::Future.resolved if pending_keyspace == keyspace
698
+
699
+ request = Protocol::QueryRequest.new("USE #{keyspace}", nil, nil, :one)
700
+
701
+ f = connection.send_request(request, timeout).map do |r|
702
+ case r
703
+ when Protocol::SetKeyspaceResultResponse
704
+ @keyspace = r.keyspace
705
+ nil
706
+ when Protocol::DetailedErrorResponse
707
+ raise Errors::QueryError.new(r.code, r.message, request.cql, r.details)
708
+ when Protocol::ErrorResponse
709
+ raise Errors::QueryError.new(r.code, r.message, request.cql, nil)
710
+ else
711
+ raise "unexpected response #{r.inspect}"
712
+ end
713
+ end
714
+
715
+ connection[:pending_keyspace] = keyspace
716
+ connection[:pending_switch] = f
717
+
718
+ f.on_complete do |f|
719
+ connection[:pending_switch] = nil
720
+ connection[:pending_keyspace] = nil
721
+ end
722
+
723
+ f
724
+ end
725
+
726
+ def prepare_statement(host, connection, cql, timeout)
727
+ synchronize do
728
+ pending = @preparing_statements[host]
729
+
730
+ return pending[cql] if pending.has_key?(cql)
731
+ end
732
+
733
+ request = Protocol::PrepareRequest.new(cql, false)
734
+
735
+ f = connection.send_request(request, timeout).map do |r|
736
+ case r
737
+ when Protocol::PreparedResultResponse
738
+ id = r.id
739
+ synchronize do
740
+ @prepared_statements[host][cql] = id
741
+ @preparing_statements[host].delete(cql)
742
+ end
743
+ id
744
+ when Protocol::DetailedErrorResponse
745
+ raise Errors::QueryError.new(r.code, r.message, cql, r.details)
746
+ when Protocol::ErrorResponse
747
+ raise Errors::QueryError.new(r.code, r.message, cql, nil)
748
+ else
749
+ raise "unexpected response #{r.inspect}"
750
+ end
751
+ end
752
+
753
+ synchronize do
754
+ @preparing_statements[host][cql] = f
755
+ end
756
+
757
+ f
758
+ end
759
+
760
+ def create_execution_info(keyspace, statement, options, request, response, hosts)
761
+ trace_id = response.trace_id
762
+ trace = trace_id ? Execution::Trace.new(trace_id, self) : nil
763
+ info = Execution::Info.new(keyspace, statement, options, hosts, request.consistency, request.retries, trace)
764
+ end
765
+ end
766
+ end
767
+ end