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,16 @@
1
+ # Copyright 2013-2014 DataStax, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ #++
15
+
16
+ require 'cassandra/auth/providers/password'
@@ -0,0 +1,73 @@
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 Auth
21
+ module Providers
22
+ # Auth provider used for Cassandra's built in authentication.
23
+ #
24
+ # @note No need to instantiate this class manually, use `:username` and
25
+ # `:password` options when calling {Cassandra.connect} and one will be
26
+ # created automatically for you.
27
+ class Password < Provider
28
+ # Authenticator used for Cassandra's built in authentication,
29
+ # see {Cassandra::Auth::Providers::Password}
30
+ # @private
31
+ class Authenticator
32
+ # @private
33
+ def initialize(username, password)
34
+ @username = username
35
+ @password = password
36
+ end
37
+
38
+ def initial_response
39
+ "\x00#{@username}\x00#{@password}"
40
+ end
41
+
42
+ def challenge_response(token)
43
+ end
44
+
45
+ def authentication_successful(token)
46
+ end
47
+ end
48
+
49
+ # @param username [String] username to use for authentication to Cassandra
50
+ # @param password [String] password to use for authentication to Cassandra
51
+ def initialize(username, password)
52
+ @username = username
53
+ @password = password
54
+ end
55
+
56
+ # Returns a Password Authenticator only if `org.apache.cassandra.auth.PasswordAuthenticator` is given.
57
+ # @param authentication_class [String] must equal to `org.apache.cassandra.auth.PasswordAuthenticator`
58
+ # @return [Cassandra::Auth::Authenticator] when `authentication_class == "org.apache.cassandra.auth.PasswordAuthenticator"`
59
+ # @return [nil] for all other values of `authentication_class`
60
+ def create_authenticator(authentication_class)
61
+ if authentication_class == PASSWORD_AUTHENTICATOR_FQCN
62
+ Authenticator.new(@username, @password)
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ # @private
69
+ PASSWORD_AUTHENTICATOR_FQCN = 'org.apache.cassandra.auth.PasswordAuthenticator'.freeze
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,144 @@
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
+ # A CQL client manages connections to one or more Cassandra nodes and you use
21
+ # it run queries, insert and update data.
22
+ #
23
+ # Client instances are threadsafe.
24
+ #
25
+ # See {Cassandra::Client::Client} for the full client API, or {Cassandra::Client.connect}
26
+ # for the options available when connecting.
27
+ #
28
+ # @example Connecting and changing to a keyspace
29
+ # # create a client and connect to two Cassandra nodes
30
+ # client = Cassandra::Client.connect(hosts: %w[node01.cassandra.local node02.cassandra.local])
31
+ # # change to a keyspace
32
+ # client.use('stuff')
33
+ #
34
+ # @example Query for data
35
+ # rows = client.execute('SELECT * FROM things WHERE id = 2')
36
+ # rows.each do |row|
37
+ # p row
38
+ # end
39
+ #
40
+ # @example Inserting and updating data
41
+ # client.execute("INSERT INTO things (id, value) VALUES (4, 'foo')")
42
+ # client.execute("UPDATE things SET value = 'bar' WHERE id = 5")
43
+ #
44
+ # @example Prepared statements
45
+ # statement = client.prepare('INSERT INTO things (id, value) VALUES (?, ?)')
46
+ # statement.execute(9, 'qux')
47
+ # statement.execute(8, 'baz')
48
+ # @private
49
+ module Client
50
+ InvalidKeyspaceNameError = Class.new(Errors::ClientError)
51
+
52
+ # @private
53
+ module SynchronousBacktrace
54
+ def synchronous_backtrace
55
+ yield
56
+ rescue Error => e
57
+ new_backtrace = caller
58
+ if new_backtrace.first.include?(SYNCHRONOUS_BACKTRACE_METHOD_NAME)
59
+ new_backtrace = new_backtrace.drop(1)
60
+ end
61
+ e.set_backtrace(new_backtrace)
62
+ raise
63
+ end
64
+
65
+ private
66
+
67
+ SYNCHRONOUS_BACKTRACE_METHOD_NAME = 'synchronous_backtrace'
68
+ end
69
+
70
+ # Create a new client and connect to Cassandra.
71
+ #
72
+ # By default the client will connect to localhost port 9042, which can be
73
+ # overridden with the `:hosts` and `:port` options, respectively. Once
74
+ # connected to the hosts given in `:hosts` the rest of the nodes in the
75
+ # cluster will automatically be discovered and connected to.
76
+ #
77
+ # If you have a multi data center setup the client will connect to all nodes
78
+ # in the data centers where the nodes you pass to `:hosts` are located. So
79
+ # if you only want to connect to nodes in one data center, make sure that
80
+ # you only specify nodes in that data center in `:hosts`.
81
+ #
82
+ # The connection will succeed if at least one node is up and accepts the
83
+ # connection. Nodes that don't respond within the specified timeout, or
84
+ # where the connection initialization fails for some reason, are ignored.
85
+ #
86
+ # @param [Hash] options
87
+ # @option options [Array<String>] :hosts (['localhost']) One or more
88
+ # hostnames used as seed nodes when connecting. Duplicates will be removed.
89
+ # @option options [String] :port (9042) The port to connect to, this port
90
+ # will be used for all nodes. Because the `system.peers` table does not
91
+ # contain the port that the nodes are listening on, the port must be the
92
+ # same for all nodes.
93
+ # @option options [Integer] :connection_timeout (5) Max time to wait for a
94
+ # connection, in seconds.
95
+ # @option options [String] :keyspace The keyspace to change to immediately
96
+ # after all connections have been established, this is optional.
97
+ # @option options [Hash] :credentials When using Cassandra's built in
98
+ # authentication you can provide your username and password through this
99
+ # option. Example: `:credentials => {:username => 'cassandra', :password => 'cassandra'}`
100
+ # @option options [Object] :auth_provider When using custom authentication
101
+ # use this option to specify the auth provider that will handle the
102
+ # authentication negotiation. See {Cassandra::Client::AuthProvider} for more info.
103
+ # @option options [Integer] :connections_per_node (1) The number of
104
+ # connections to open to each node. Each connection can have 128
105
+ # concurrent requests, so unless you have a need for more than that (times
106
+ # the number of nodes in your cluster), leave this option at its default.
107
+ # @option options [Integer] :default_consistency (:quorum) The consistency
108
+ # to use unless otherwise specified. Consistency can also be specified on
109
+ # a per-request basis.
110
+ # @option options [Cassandra::Compression::Compressor] :compressor An object that
111
+ # can compress and decompress frames. By specifying this option frame
112
+ # compression will be enabled. If the server does not support compression
113
+ # or the specific compression algorithm specified by the compressor,
114
+ # compression will not be enabled and a warning will be logged.
115
+ # @option options [String] :cql_version Specifies which CQL version the
116
+ # server should expect.
117
+ # @option options [Integer] :logger If you want the client to log
118
+ # significant events pass an object implementing the standard Ruby logger
119
+ # interface (e.g. quacks like `Logger` from the standard library) with
120
+ # this option.
121
+ # @raise Cassandra::Io::ConnectionError when a connection couldn't be established
122
+ # to any node
123
+ # @raise Cassandra::Errors::QueryError when the specified keyspace does not exist
124
+ # or when the specifed CQL version is not supported.
125
+ # @return [Cassandra::Client::Client]
126
+ def self.connect(options={})
127
+ SynchronousClient.new(AsynchronousClient.new(options)).connect
128
+ end
129
+ end
130
+ end
131
+
132
+ require 'cassandra/client/connection_manager'
133
+ require 'cassandra/client/connector'
134
+ require 'cassandra/client/null_logger'
135
+ require 'cassandra/client/column_metadata'
136
+ require 'cassandra/client/result_metadata'
137
+ require 'cassandra/client/execute_options_decoder'
138
+ require 'cassandra/client/client'
139
+ require 'cassandra/client/prepared_statement'
140
+ require 'cassandra/client/batch'
141
+ require 'cassandra/client/query_result'
142
+ require 'cassandra/client/void_result'
143
+ require 'cassandra/client/request_runner'
144
+ require 'cassandra/client/peer_discovery'
@@ -0,0 +1,212 @@
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
+ # Batches let you send multiple queries (`INSERT`, `UPDATE` and `DELETE`) in
22
+ # one go. This can lead to better performance, and depending on the options
23
+ # you specify can also give you different consistency guarantees.
24
+ #
25
+ # Batches can contain a mix of different queries and prepared statements.
26
+ #
27
+ # @see Cassandra::Client::Client#batch
28
+ class Batch
29
+ # @!method add(cql_or_prepared_statement, *bound_values)
30
+ #
31
+ # Add a query or a prepared statement to the batch.
32
+ #
33
+ # @example Adding a mix of statements to a batch
34
+ # batch.add(%(UPDATE people SET name = 'Miriam' WHERE id = 3435))
35
+ # batch.add(%(UPDATE people SET name = ? WHERE id = ?), 'Miriam', 3435)
36
+ # batch.add(prepared_statement, 'Miriam', 3435)
37
+ #
38
+ # @param [String, Cassandra::Client::PreparedStatement] cql_or_prepared_statement
39
+ # a CQL string or a prepared statement object (obtained through
40
+ # {Cassandra::Client::Client#prepare})
41
+ # @param [Array] bound_values a list of bound values -- only applies when
42
+ # adding prepared statements and when there are binding markers in the
43
+ # given CQL. If the last argument is a hash and it has the key
44
+ # `:type_hints` this will be passed as type hints to the request encoder
45
+ # (if the last argument is any other hash it will be assumed to be a
46
+ # bound value of type MAP). See {Cassandra::Client::Client#execute} for more
47
+ # info on type hints.
48
+ # @return [nil]
49
+
50
+ # @!method execute(options={})
51
+ #
52
+ # Execute the batch and return the result.
53
+ #
54
+ # @param [Hash] options an options hash or a symbol (as a shortcut for
55
+ # specifying the consistency), see {Cassandra::Client::Client#execute} for
56
+ # full details about how this value is interpreted.
57
+ # @raise [Cassandra::Errors::QueryError] raised when there is an error on the server side
58
+ # @raise [Cassandra::Errors::NotPreparedError] raised in the unlikely event that a
59
+ # prepared statement was not prepared on the chosen connection
60
+ # @return [Cassandra::Client::VoidResult] a batch always returns a void result
61
+ end
62
+
63
+ # A convenient wrapper that makes it easy to build batches of multiple
64
+ # executions of the same prepared statement.
65
+ #
66
+ # @see Cassandra::Client::PreparedStatement#batch
67
+ class PreparedStatementBatch
68
+ # @!method add(*bound_values)
69
+ #
70
+ # Add the statement to the batch with the specified bound values.
71
+ #
72
+ # @param [Array] bound_values the values to bind to the added statement,
73
+ # see {Cassandra::Client::PreparedStatement#execute}.
74
+ # @return [nil]
75
+
76
+ # @!method execute(options={})
77
+ #
78
+ # Execute the batch and return the result.
79
+ #
80
+ # @raise [Cassandra::Errors::QueryError] raised when there is an error on the server side
81
+ # @raise [Cassandra::Errors::NotPreparedError] raised in the unlikely event that a
82
+ # prepared statement was not prepared on the chosen connection
83
+ # @return [Cassandra::Client::VoidResult] a batch always returns a void result
84
+ end
85
+
86
+ # @private
87
+ class AsynchronousBatch < Batch
88
+ def initialize(type, execute_options_decoder, connection_manager, options=nil)
89
+ raise ArgumentError, "Unknown batch type: #{type}" unless BATCH_TYPES.include?(type)
90
+ @type = type
91
+ @execute_options_decoder = execute_options_decoder
92
+ @connection_manager = connection_manager
93
+ @options = options
94
+ @request_runner = RequestRunner.new
95
+ @parts = []
96
+ end
97
+
98
+ def add(*args)
99
+ @parts << args
100
+ nil
101
+ end
102
+
103
+ def execute(options=nil)
104
+ options = @execute_options_decoder.decode_options(@options, options)
105
+ connection = @connection_manager.random_connection
106
+ request = Protocol::BatchRequest.new(BATCH_TYPES[@type], options[:consistency], options[:trace])
107
+ unprepared_statements = nil
108
+ @parts.each do |part, *bound_args|
109
+ if part.is_a?(String) || part.prepared?(connection)
110
+ add_part(connection, request, part, bound_args)
111
+ else
112
+ unprepared_statements ||= []
113
+ unprepared_statements << [part, bound_args]
114
+ end
115
+ end
116
+ @parts = []
117
+ if unprepared_statements.nil?
118
+ @request_runner.execute(connection, request, options[:timeout])
119
+ else
120
+ fs = unprepared_statements.map do |statement, _|
121
+ if statement.respond_to?(:async)
122
+ statement.async.prepare(connection)
123
+ else
124
+ statement.prepare(connection)
125
+ end
126
+ end
127
+ Ione::Future.all(*fs).flat_map do
128
+ unprepared_statements.each do |statement, bound_args|
129
+ add_part(connection, request, statement, bound_args)
130
+ end
131
+ @request_runner.execute(connection, request, options[:timeout])
132
+ end
133
+ end
134
+ end
135
+
136
+ private
137
+
138
+ BATCH_TYPES = {
139
+ :logged => Protocol::BatchRequest::LOGGED_TYPE,
140
+ :unlogged => Protocol::BatchRequest::UNLOGGED_TYPE,
141
+ :counter => Protocol::BatchRequest::COUNTER_TYPE,
142
+ }.freeze
143
+
144
+ def add_part(connection, request, part, bound_args)
145
+ if part.is_a?(String)
146
+ type_hints = nil
147
+ if bound_args.last.is_a?(Hash) && bound_args.last.include?(:type_hints)
148
+ bound_args = bound_args.dup
149
+ type_hints = bound_args.pop[:type_hints]
150
+ end
151
+ request.add_query(part, bound_args, type_hints)
152
+ else
153
+ part.add_to_batch(request, connection, bound_args)
154
+ end
155
+ end
156
+ end
157
+
158
+ # @private
159
+ class SynchronousBatch < Batch
160
+ include SynchronousBacktrace
161
+
162
+ def initialize(asynchronous_batch)
163
+ @asynchronous_batch = asynchronous_batch
164
+ end
165
+
166
+ def async
167
+ @asynchronous_batch
168
+ end
169
+
170
+ def add(*args)
171
+ @asynchronous_batch.add(*args)
172
+ end
173
+
174
+ def execute(options=nil)
175
+ synchronous_backtrace { @asynchronous_batch.execute(options).value }
176
+ end
177
+ end
178
+
179
+ # @private
180
+ class AsynchronousPreparedStatementBatch < PreparedStatementBatch
181
+ def initialize(prepared_statement, batch)
182
+ @prepared_statement = prepared_statement
183
+ @batch = batch
184
+ end
185
+
186
+ def add(*args)
187
+ @batch.add(@prepared_statement, *args)
188
+ end
189
+
190
+ def execute(options=nil)
191
+ @batch.execute(options)
192
+ end
193
+ end
194
+
195
+ # @private
196
+ class SynchronousPreparedStatementBatch < PreparedStatementBatch
197
+ include SynchronousBacktrace
198
+
199
+ def initialize(asynchronous_batch)
200
+ @asynchronous_batch = asynchronous_batch
201
+ end
202
+
203
+ def add(*args)
204
+ @asynchronous_batch.add(*args)
205
+ end
206
+
207
+ def execute(options=nil)
208
+ synchronous_backtrace { @asynchronous_batch.execute(options).value }
209
+ end
210
+ end
211
+ end
212
+ end
@@ -0,0 +1,591 @@
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 CQL client manages connections to one or more Cassandra nodes and you use
22
+ # it run queries, insert and update data, prepare statements and switch
23
+ # keyspaces.
24
+ #
25
+ # To get a reference to a client you call {Cassandra::Client.connect}. When you
26
+ # don't need the client anymore you can call {#close} to close all connections.
27
+ #
28
+ # Internally the client runs an IO reactor in a background thread. The reactor
29
+ # handles all IO and manages the connections to the Cassandra nodes. This
30
+ # makes it possible for the client to handle highly concurrent applications
31
+ # very efficiently.
32
+ #
33
+ # Client instances are threadsafe and you only need a single instance for in
34
+ # an application. Using multiple instances is more likely to lead to worse
35
+ # performance than better.
36
+ #
37
+ # Because the client opens sockets, and runs threads it cannot be used by
38
+ # the child created when forking a process. If your application forks (for
39
+ # example applications running in the Unicorn application server or Resque
40
+ # task queue) you _must_ connect after forking.
41
+ #
42
+ # @see Cassandra::Client.connect
43
+ class Client
44
+ # @!method close
45
+ #
46
+ # Disconnect from all nodes.
47
+ #
48
+ # @return [Cassandra::Client]
49
+
50
+ # @!method connected?
51
+ #
52
+ # Returns whether or not the client is connected.
53
+ #
54
+ # @return [true, false]
55
+
56
+ # @!method keyspace
57
+ #
58
+ # Returns the name of the current keyspace, or `nil` if no keyspace has been
59
+ # set yet.
60
+ #
61
+ # @return [String]
62
+
63
+ # @!method use(keyspace)
64
+ #
65
+ # Changes keyspace by sending a `USE` statement to all connections.
66
+ #
67
+ # The the second parameter is meant for internal use only.
68
+ #
69
+ # @param [String] keyspace
70
+ # @raise [Cassandra::Errors::NotConnectedError] raised when the client is not connected
71
+ # @return [nil]
72
+
73
+ # @!method execute(cql, *values, options={})
74
+ #
75
+ # Execute a CQL statement, optionally passing bound values.
76
+ #
77
+ # When passing bound values the request encoder will have to guess what
78
+ # types to encode the values as. For most types this will be no problem,
79
+ # but for integers and floating point numbers the larger size will be
80
+ # chosen (e.g. `BIGINT` and `DOUBLE` and not `INT` and `FLOAT`). You can
81
+ # override the guessing with the `:type_hint` option. Don't use on-the-fly
82
+ # bound values when you will issue the request multiple times, prepared
83
+ # statements are almost always a better choice.
84
+ #
85
+ # _Please note that on-the-fly bound values are only supported by Cassandra
86
+ # 2.0 and above._
87
+ #
88
+ # @example A simple CQL query
89
+ # result = client.execute("SELECT * FROM users WHERE user_name = 'sue'")
90
+ # result.each do |row|
91
+ # p row
92
+ # end
93
+ #
94
+ # @example Using on-the-fly bound values
95
+ # client.execute('INSERT INTO users (user_name, full_name) VALUES (?, ?)', 'sue', 'Sue Smith')
96
+ #
97
+ # @example Using on-the-fly bound values with type hints
98
+ # client.execute('INSERT INTO users (user_name, age) VALUES (?, ?)', 'sue', 33, type_hints: [nil, :int])
99
+ #
100
+ # @example Specifying the consistency as a symbol
101
+ # client.execute("UPDATE users SET full_name = 'Sue S. Smith' WHERE user_name = 'sue'", consistency: :one)
102
+ #
103
+ # @example Specifying the consistency and other options
104
+ # client.execute("SELECT * FROM users", consistency: :all, timeout: 1.5)
105
+ #
106
+ # @example Loading a big result page by page
107
+ # result_page = client.execute("SELECT * FROM large_table WHERE id = 'partition_with_lots_of_data'", page_size: 100)
108
+ # while result_page
109
+ # result_page.each do |row|
110
+ # p row
111
+ # end
112
+ # result_page = result_page.next_page
113
+ # end
114
+ #
115
+ # @example Activating tracing for a query
116
+ # result = client.execute("SELECT * FROM users", tracing: true)
117
+ # p result.trace_id
118
+ #
119
+ # @param [String] cql
120
+ # @param [Array] values Values to bind to any binding markers in the
121
+ # query (i.e. "?" placeholders) -- using this feature is similar to
122
+ # using a prepared statement, but without the type checking. The client
123
+ # needs to guess which data types to encode the values as, and will err
124
+ # on the side of caution, using types like BIGINT instead of INT for
125
+ # integers, and DOUBLE instead of FLOAT for floating point numbers. It
126
+ # is not recommended to use this feature for anything but convenience,
127
+ # and the algorithm used to guess types is to be considered experimental.
128
+ # @param [Hash] options
129
+ # @option options [Symbol] :consistency (:quorum) The
130
+ # consistency to use for this query.
131
+ # @option options [Symbol] :serial_consistency (nil) The
132
+ # consistency to use for conditional updates (`:serial` or
133
+ # `:local_serial`), see the CQL documentation for the semantics of
134
+ # serial consistencies and conditional updates. The default is assumed
135
+ # to be `:serial` by the server if none is specified. Ignored for non-
136
+ # conditional queries.
137
+ # @option options [Integer] :timeout (nil) How long to wait
138
+ # for a response. If this timeout expires a {Cassandra::TimeoutError} will
139
+ # be raised.
140
+ # @option options [Boolean] :trace (false) Request tracing
141
+ # for this request. See {Cassandra::Client::QueryResult} and
142
+ # {Cassandra::Client::VoidResult} for how to retrieve the tracing data.
143
+ # @option options [Integer] :page_size (nil) Instead of
144
+ # returning all rows, return the response in pages of this size. The
145
+ # first result will contain the first page, to load subsequent pages
146
+ # use {Cassandra::Client::QueryResult#next_page}.
147
+ # @option options [Array] :type_hints (nil) When passing
148
+ # on-the-fly bound values the request encoder will have to guess what
149
+ # types to encode the values as. Using this option you can give it hints
150
+ # and avoid it guessing wrong. The hints must be an array that has the
151
+ # same number of arguments as the number of bound values, and each
152
+ # element should be the type of the corresponding value, or nil if you
153
+ # prefer the encoder to guess. The types should be provided as lower
154
+ # case symbols, e.g. `:int`, `:time_uuid`, etc.
155
+ # @raise [Cassandra::Errors::NotConnectedError] raised when the client is not connected
156
+ # @raise [Cassandra::TimeoutError] raised when a timeout was specified and no
157
+ # response was received within the timeout.
158
+ # @raise [Cassandra::Errors::QueryError] raised when the CQL has syntax errors or for
159
+ # other situations when the server complains.
160
+ # @return [nil, Cassandra::Client::QueryResult, Cassandra::Client::VoidResult] Some
161
+ # queries have no result and return `nil`, but `SELECT` statements
162
+ # return an `Enumerable` of rows (see {Cassandra::Client::QueryResult}), and
163
+ # `INSERT` and `UPDATE` return a similar type
164
+ # (see {Cassandra::Client::VoidResult}).
165
+
166
+ # @!method prepare(cql)
167
+ #
168
+ # Returns a prepared statement that can be run over and over again with
169
+ # different bound values.
170
+ #
171
+ # @see Cassandra::Client::PreparedStatement
172
+ # @param [String] cql The CQL to prepare
173
+ # @raise [Cassandra::Errors::NotConnectedError] raised when the client is not connected
174
+ # @raise [Cassandra::Errors::IoError] raised when there is an IO error, for example
175
+ # if the server suddenly closes the connection
176
+ # @raise [Cassandra::Errors::QueryError] raised when there is an error on the server
177
+ # side, for example when you specify a malformed CQL query
178
+ # @return [Cassandra::Client::PreparedStatement] an object encapsulating the
179
+ # prepared statement
180
+
181
+ # @!method batch(type=:logged, options={})
182
+ #
183
+ # Yields a batch when called with a block. The batch is automatically
184
+ # executed at the end of the block and the result is returned.
185
+ #
186
+ # Returns a batch when called wihtout a block. The batch will remember
187
+ # the options given and merge these with any additional options given
188
+ # when {Cassandra::Client::Batch#execute} is called.
189
+ #
190
+ # Please note that the batch object returned by this method _is not thread
191
+ # safe_.
192
+ #
193
+ # The type parameter can be ommitted and the options can then be given
194
+ # as first parameter.
195
+ #
196
+ # @example Executing queries in a batch
197
+ # client.batch do |batch|
198
+ # batch.add(%(INSERT INTO metrics (id, time, value) VALUES (1234, NOW(), 23423)))
199
+ # batch.add(%(INSERT INTO metrics (id, time, value) VALUES (2346, NOW(), 13)))
200
+ # batch.add(%(INSERT INTO metrics (id, time, value) VALUES (2342, NOW(), 2367)))
201
+ # batch.add(%(INSERT INTO metrics (id, time, value) VALUES (4562, NOW(), 1231)))
202
+ # end
203
+ #
204
+ # @example Using the returned batch object
205
+ # batch = client.batch(:counter, trace: true)
206
+ # batch.add('UPDATE counts SET value = value + ? WHERE id = ?', 4, 87654)
207
+ # batch.add('UPDATE counts SET value = value + ? WHERE id = ?', 3, 6572)
208
+ # result = batch.execute(timeout: 10)
209
+ # puts result.trace_id
210
+ #
211
+ # @example Providing type hints for on-the-fly bound values
212
+ # batch = client.batch
213
+ # batch.add('UPDATE counts SET value = value + ? WHERE id = ?', 4, type_hints: [:int])
214
+ # batch.execute
215
+ #
216
+ # @see Cassandra::Client::Batch
217
+ # @param [Symbol] type the type of batch, must be one of `:logged`,
218
+ # `:unlogged` and `:counter`. The precise meaning of these is defined
219
+ # in the CQL specification.
220
+ # @yieldparam [Cassandra::Client::Batch] batch the batch
221
+ # @return [Cassandra::Client::VoidResult, Cassandra::Client::Batch] when no block is
222
+ # given the batch is returned, when a block is given the result of
223
+ # executing the batch is returned (see {Cassandra::Client::Batch#execute}).
224
+ end
225
+
226
+ # @private
227
+ class AsynchronousClient < Client
228
+ def initialize(options={})
229
+ @compressor = options[:compressor]
230
+ @cql_version = options[:cql_version]
231
+ @logger = options[:logger] || NullLogger.new
232
+ @protocol_version = options[:protocol_version] || 2
233
+ @io_reactor = options[:io_reactor] || Io::IoReactor.new
234
+ @hosts = extract_hosts(options)
235
+ @initial_keyspace = options[:keyspace]
236
+ @connections_per_node = options[:connections_per_node] || 1
237
+ @lock = Mutex.new
238
+ @request_runner = RequestRunner.new
239
+ @connection_manager = ConnectionManager.new
240
+ @execute_options_decoder = ExecuteOptionsDecoder.new(options[:default_consistency] || DEFAULT_CONSISTENCY)
241
+ @port = options[:port] || DEFAULT_PORT
242
+ @connection_timeout = options[:connection_timeout] || DEFAULT_CONNECTION_TIMEOUT
243
+ @credentials = options[:credentials]
244
+ @auth_provider = options[:auth_provider] || @credentials && Auth::Providers::Password.new(*@credentials.values_at(:username, :password))
245
+ @connected = false
246
+ @connecting = false
247
+ @closing = false
248
+ end
249
+
250
+ def connect
251
+ @lock.synchronize do
252
+ raise Errors::ClientError, 'Cannot connect a closed client' if @closing || @closed
253
+ return @connected_future if can_execute?
254
+ @connecting = true
255
+ @connected_future = begin
256
+ f = @io_reactor.start
257
+ f = f.flat_map { connect_with_protocol_version_fallback }
258
+ f = f.flat_map { |connections| connect_to_all_peers(connections) }
259
+ f = f.flat_map do |connections|
260
+ @connection_manager.add_connections(connections)
261
+ register_event_listener(@connection_manager.random_connection)
262
+ end
263
+ f = f.flat_map { use_keyspace(@connection_manager.snapshot, @initial_keyspace) }
264
+ f.map(self)
265
+ end
266
+ end
267
+ @connected_future.on_complete(&method(:connected))
268
+ @connected_future
269
+ end
270
+
271
+ def close
272
+ @lock.synchronize do
273
+ return @closed_future if @closing
274
+ @closing = true
275
+ @closed_future = begin
276
+ if @connecting
277
+ f = @connected_future.recover
278
+ f = f.flat_map { @io_reactor.stop }
279
+ f = f.map(self)
280
+ f
281
+ else
282
+ f = @io_reactor.stop
283
+ f = f.map(self)
284
+ f
285
+ end
286
+ end
287
+ end
288
+ @closed_future.on_complete(&method(:closed))
289
+ @closed_future
290
+ end
291
+
292
+ def connected?
293
+ @connected
294
+ end
295
+
296
+ def keyspace
297
+ @connection_manager.random_connection.keyspace
298
+ end
299
+
300
+ def use(keyspace)
301
+ with_failure_handler do
302
+ connections = @connection_manager.reject { |c| c.keyspace == keyspace }
303
+ return Ione::Future.resolved if connections.empty?
304
+ use_keyspace(connections, keyspace).map(nil)
305
+ end
306
+ end
307
+
308
+ def execute(cql, *args)
309
+ with_failure_handler do
310
+ options_or_consistency = nil
311
+ if args.last.is_a?(Symbol) || args.last.is_a?(Hash)
312
+ options_or_consistency = args.pop
313
+ end
314
+ options = @execute_options_decoder.decode_options(options_or_consistency)
315
+ request = Protocol::QueryRequest.new(cql, args, options[:type_hints], options[:consistency], options[:serial_consistency], options[:page_size], options[:paging_state], options[:trace])
316
+ f = execute_request(request, options[:timeout])
317
+ if options.include?(:page_size)
318
+ f = f.map { |result| AsynchronousQueryPagedQueryResult.new(self, request, result, options) }
319
+ end
320
+ f
321
+ end
322
+ end
323
+
324
+ def prepare(cql)
325
+ with_failure_handler do
326
+ AsynchronousPreparedStatement.prepare(cql, @execute_options_decoder, @connection_manager, @logger)
327
+ end
328
+ end
329
+
330
+ def batch(type=:logged, options=nil)
331
+ if type.is_a?(Hash)
332
+ options = type
333
+ type = :logged
334
+ end
335
+ b = AsynchronousBatch.new(type, @execute_options_decoder, @connection_manager, options)
336
+ if block_given?
337
+ yield b
338
+ b.execute
339
+ else
340
+ b
341
+ end
342
+ end
343
+
344
+ private
345
+
346
+ DEFAULT_CQL_VERSIONS = {1 => '3.0.0'}
347
+ DEFAULT_CQL_VERSIONS.default = '3.1.0'
348
+ DEFAULT_CQL_VERSIONS.freeze
349
+ DEFAULT_CONSISTENCY = :quorum
350
+ DEFAULT_PORT = 9042
351
+ DEFAULT_CONNECTION_TIMEOUT = 10
352
+ MAX_RECONNECTION_ATTEMPTS = 5
353
+
354
+ def extract_hosts(options)
355
+ if options[:hosts] && options[:hosts].any?
356
+ options[:hosts].uniq
357
+ elsif options[:host]
358
+ options[:host].split(',').uniq
359
+ else
360
+ %w[localhost]
361
+ end
362
+ end
363
+
364
+ def create_cluster_connector
365
+ cql_version = @cql_version || DEFAULT_CQL_VERSIONS[@protocol_version]
366
+ authentication_step = @protocol_version == 1 ? CredentialsAuthenticationStep.new(@credentials) : SaslAuthenticationStep.new(@auth_provider)
367
+ protocol_handler_factory = lambda { |connection| Protocol::CqlProtocolHandler.new(connection, @io_reactor, @protocol_version, @compressor) }
368
+ ClusterConnector.new(
369
+ Connector.new([
370
+ ConnectStep.new(@io_reactor, protocol_handler_factory, @port, @connection_timeout, @logger),
371
+ CacheOptionsStep.new,
372
+ InitializeStep.new(@compressor, @logger),
373
+ authentication_step,
374
+ CachePropertiesStep.new,
375
+ ]),
376
+ @logger
377
+ )
378
+ end
379
+
380
+ def connect_with_protocol_version_fallback
381
+ f = create_cluster_connector.connect_all(@hosts, @connections_per_node)
382
+ f.fallback do |error|
383
+ if error.is_a?(Errors::QueryError) && error.code == 0x0a && @protocol_version > 1
384
+ @logger.warn('Could not connect using protocol version %d (will try again with %d): %s' % [@protocol_version, @protocol_version - 1, error.message])
385
+ @protocol_version -= 1
386
+ connect_with_protocol_version_fallback
387
+ else
388
+ raise error
389
+ end
390
+ end
391
+ end
392
+
393
+ def connect_to_all_peers(seed_connections, initial_keyspace=@initial_keyspace)
394
+ @logger.debug('Looking for additional nodes')
395
+ peer_discovery = PeerDiscovery.new(seed_connections)
396
+ peer_discovery.new_hosts.flat_map do |hosts|
397
+ if hosts.empty?
398
+ @logger.debug('No additional nodes found')
399
+ Ione::Future.resolved(seed_connections)
400
+ else
401
+ @logger.debug('%d additional nodes found' % hosts.size)
402
+ f = create_cluster_connector.connect_all(hosts, @connections_per_node)
403
+ f = f.map do |discovered_connections|
404
+ seed_connections + discovered_connections
405
+ end
406
+ f.recover(seed_connections)
407
+ end
408
+ end
409
+ end
410
+
411
+ def connected(f)
412
+ if f.resolved?
413
+ @lock.synchronize do
414
+ @connecting = false
415
+ @connected = true
416
+ end
417
+ @logger.info('Cluster connection complete')
418
+ else
419
+ @lock.synchronize do
420
+ @connecting = false
421
+ @connected = false
422
+ end
423
+ f.on_failure do |e|
424
+ @logger.error('Failed connecting to cluster: %s' % e.message)
425
+ end
426
+ close
427
+ end
428
+ end
429
+
430
+ def closed(f)
431
+ @lock.synchronize do
432
+ @closing = false
433
+ @closed = true
434
+ @connected = false
435
+ if f.resolved?
436
+ @logger.info('Cluster disconnect complete')
437
+ else
438
+ f.on_failure do |e|
439
+ @logger.error('Cluster disconnect failed: %s' % e.message)
440
+ end
441
+ end
442
+ end
443
+ end
444
+
445
+ def can_execute?
446
+ !@closing && (@connecting || (@connected && @connection_manager.connected?))
447
+ end
448
+
449
+ def with_failure_handler
450
+ return Ione::Future.failed(Errors::NotConnectedError.new) unless can_execute?
451
+ yield
452
+ rescue => e
453
+ Ione::Future.failed(e)
454
+ end
455
+
456
+ def use_keyspace(connections, keyspace)
457
+ return Ione::Future.resolved(connections) unless keyspace
458
+ return Ione::Future.failed(InvalidKeyspaceNameError.new(%("#{keyspace}" is not a valid keyspace name))) unless keyspace =~ /^\w[\w\d_]*$|^"\w[\w\d_]*"$/
459
+
460
+ futures = connections.map do |connection|
461
+ request = Protocol::QueryRequest.new("USE #{keyspace}", nil, nil, :one)
462
+ @request_runner.execute(connection, request).map(connection)
463
+ end
464
+ Ione::Future.all(*futures)
465
+ end
466
+
467
+ def register_event_listener(connection)
468
+ register_request = Protocol::RegisterRequest.new(Protocol::TopologyChangeEventResponse::TYPE, Protocol::StatusChangeEventResponse::TYPE)
469
+ f = execute_request(register_request, nil, connection)
470
+ f.on_value do
471
+ connection.on_closed do
472
+ if connected?
473
+ begin
474
+ register_event_listener(@connection_manager.random_connection)
475
+ rescue Errors::NotConnectedError
476
+ # we had started closing down after the connection check
477
+ end
478
+ end
479
+ end
480
+ connection.on_event do |event|
481
+ if event.change == 'UP' || event.change == 'NEW_NODE'
482
+ @logger.debug('Received %s event' % event.change)
483
+ unless @looking_for_nodes
484
+ @looking_for_nodes = true
485
+ handle_topology_change.on_complete do |f|
486
+ @looking_for_nodes = false
487
+ end
488
+ end
489
+ end
490
+ end
491
+ end
492
+ f
493
+ end
494
+
495
+ def handle_topology_change(remaning_attempts=MAX_RECONNECTION_ATTEMPTS)
496
+ with_failure_handler do
497
+ seed_connections = @connection_manager.snapshot
498
+ f = connect_to_all_peers(seed_connections, keyspace)
499
+ f.flat_map do |all_connections|
500
+ new_connections = all_connections - seed_connections
501
+ if new_connections.size > 0
502
+ f = use_keyspace(new_connections, keyspace)
503
+ f.on_value do
504
+ @connection_manager.add_connections(new_connections)
505
+ end
506
+ f
507
+ elsif remaning_attempts > 0
508
+ timeout = 2**(MAX_RECONNECTION_ATTEMPTS - remaning_attempts)
509
+ @logger.debug('Scheduling new peer discovery in %ds' % timeout)
510
+ f = @io_reactor.schedule_timer(timeout)
511
+ f.flat_map do
512
+ handle_topology_change(remaning_attempts - 1)
513
+ end
514
+ else
515
+ @logger.warn('Giving up looking for additional nodes')
516
+ Ione::Future.resolved
517
+ end
518
+ end
519
+ end
520
+ end
521
+
522
+ def execute_request(request, timeout=nil, connection=nil)
523
+ f = @request_runner.execute(connection || @connection_manager.random_connection, request, timeout)
524
+ f.map do |result|
525
+ if result.is_a?(KeyspaceChanged)
526
+ use(result.keyspace)
527
+ nil
528
+ else
529
+ result
530
+ end
531
+ end
532
+ end
533
+ end
534
+
535
+ # @private
536
+ class SynchronousClient < Client
537
+ include SynchronousBacktrace
538
+
539
+ def initialize(async_client)
540
+ @async_client = async_client
541
+ end
542
+
543
+ def connect
544
+ synchronous_backtrace { @async_client.connect.value }
545
+ self
546
+ end
547
+
548
+ def close
549
+ synchronous_backtrace { @async_client.close.value }
550
+ self
551
+ end
552
+
553
+ def connected?
554
+ @async_client.connected?
555
+ end
556
+
557
+ def keyspace
558
+ @async_client.keyspace
559
+ end
560
+
561
+ def use(keyspace)
562
+ synchronous_backtrace { @async_client.use(keyspace).value }
563
+ end
564
+
565
+ def execute(cql, *args)
566
+ synchronous_backtrace do
567
+ result = @async_client.execute(cql, *args).value
568
+ result = SynchronousPagedQueryResult.new(result) if result.is_a?(PagedQueryResult)
569
+ result
570
+ end
571
+ end
572
+
573
+ def prepare(cql)
574
+ async_statement = synchronous_backtrace { @async_client.prepare(cql).value }
575
+ SynchronousPreparedStatement.new(async_statement)
576
+ end
577
+
578
+ def batch(type=:logged, options={}, &block)
579
+ if block_given?
580
+ synchronous_backtrace { @async_client.batch(type, options, &block).value }
581
+ else
582
+ SynchronousBatch.new(@async_client.batch(type, options))
583
+ end
584
+ end
585
+
586
+ def async
587
+ @async_client
588
+ end
589
+ end
590
+ end
591
+ end