cassandra-driver 1.0.0.rc.1-java → 1.1.0-java

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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +58 -18
  3. data/lib/cassandra.rb +132 -93
  4. data/lib/cassandra/auth.rb +3 -3
  5. data/lib/cassandra/cluster.rb +65 -39
  6. data/lib/cassandra/cluster/client.rb +67 -28
  7. data/lib/cassandra/{client/connection_manager.rb → cluster/connection_pool.rb} +9 -3
  8. data/lib/cassandra/cluster/connector.rb +101 -30
  9. data/lib/cassandra/cluster/control_connection.rb +160 -96
  10. data/lib/cassandra/{client/null_logger.rb → cluster/failed_connection.rb} +12 -14
  11. data/lib/cassandra/cluster/options.rb +26 -11
  12. data/lib/cassandra/cluster/schema.rb +22 -1
  13. data/lib/cassandra/column.rb +5 -0
  14. data/lib/cassandra/driver.rb +46 -12
  15. data/lib/cassandra/errors.rb +5 -5
  16. data/lib/cassandra/execution/options.rb +42 -8
  17. data/lib/cassandra/execution/trace.rb +4 -4
  18. data/lib/cassandra/executors.rb +111 -0
  19. data/lib/cassandra/future.rb +88 -64
  20. data/lib/cassandra/keyspace.rb +12 -0
  21. data/lib/cassandra/load_balancing.rb +10 -0
  22. data/lib/cassandra/load_balancing/policies/dc_aware_round_robin.rb +10 -5
  23. data/lib/cassandra/load_balancing/policies/round_robin.rb +7 -5
  24. data/lib/cassandra/load_balancing/policies/token_aware.rb +31 -10
  25. data/lib/cassandra/load_balancing/policies/white_list.rb +4 -7
  26. data/lib/cassandra/null_logger.rb +35 -0
  27. data/lib/cassandra/protocol/cql_protocol_handler.rb +8 -1
  28. data/lib/cassandra/protocol/requests/query_request.rb +1 -11
  29. data/lib/cassandra/result.rb +34 -9
  30. data/lib/cassandra/session.rb +6 -0
  31. data/lib/cassandra/statements/prepared.rb +5 -1
  32. data/lib/cassandra/table.rb +5 -0
  33. data/lib/cassandra/util.rb +130 -0
  34. data/lib/cassandra/version.rb +1 -1
  35. metadata +40 -50
  36. data/lib/cassandra/client.rb +0 -144
  37. data/lib/cassandra/client/batch.rb +0 -212
  38. data/lib/cassandra/client/client.rb +0 -591
  39. data/lib/cassandra/client/column_metadata.rb +0 -54
  40. data/lib/cassandra/client/connector.rb +0 -273
  41. data/lib/cassandra/client/execute_options_decoder.rb +0 -59
  42. data/lib/cassandra/client/peer_discovery.rb +0 -50
  43. data/lib/cassandra/client/prepared_statement.rb +0 -314
  44. data/lib/cassandra/client/query_result.rb +0 -230
  45. data/lib/cassandra/client/request_runner.rb +0 -70
  46. data/lib/cassandra/client/result_metadata.rb +0 -48
  47. data/lib/cassandra/client/void_result.rb +0 -78
@@ -1,144 +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
- # 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::Errors::IOError] when a connection couldn't be established
122
- # to any node
123
- # @raise [Cassandra::Errors::ExecutionError] 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'
@@ -1,212 +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
- # 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::ExecutionError] 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::ExecutionError] 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
@@ -1,591 +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 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::ClientError] 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::Errors::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::ClientError] raised when the client is not connected
156
- # @raise [Cassandra::Errors::TimeoutError] raised when a timeout was specified and no
157
- # response was received within the timeout.
158
- # @raise [Cassandra::Errors::ExecutionError] 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::ClientError] 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::ExecutionError] 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] || Ione::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::ProtocolError) && @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, error.message, error.backtrace)
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::ClientError.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::IOError
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