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,79 @@
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
+ # Base class for all Errors raised by the driver
21
+ # @see Cassandra::Errors
22
+ class Error < StandardError
23
+ end
24
+
25
+ module Errors
26
+ # @!parse class IoError < StandardError; end
27
+ # @private
28
+ IoError = Ione::IoError
29
+
30
+ # This error type represents errors sent by the server, the `code` attribute
31
+ # can be used to find the exact type, and `cql` contains the request's CQL,
32
+ # if any. `message` contains the human readable error message sent by the
33
+ # server.
34
+ class QueryError < Error
35
+ # @return [Integer] error code
36
+ attr_reader :code
37
+ # @return [String] original CQL used
38
+ attr_reader :cql
39
+ # @return [Hash{Symbol => String, Integer}] various error details
40
+ attr_reader :details
41
+
42
+ # @private
43
+ def initialize(code, message, cql=nil, details=nil)
44
+ super(message)
45
+ @code = code
46
+ @cql = cql
47
+ @details = details
48
+ end
49
+ end
50
+
51
+ # This error is thrown when not hosts could be reached during connection or query execution.
52
+ class NoHostsAvailable < Error
53
+ # @return [Hash{Cassandra::Host => Exception}] a map of hosts to underlying exceptions
54
+ attr_reader :errors
55
+
56
+ # @private
57
+ def initialize(errors = {})
58
+ super("no hosts available, check #errors property for details")
59
+
60
+ @errors = errors
61
+ end
62
+ end
63
+
64
+ # Client error represents bad driver state or configuration
65
+ #
66
+ # @see Cassandra::Errors::AuthenticationError
67
+ class ClientError < Error
68
+ end
69
+
70
+ # Raised when cannot authenticate to Cassandra
71
+ class AuthenticationError < ClientError
72
+ end
73
+
74
+ # @private
75
+ NotConnectedError = Class.new(Error)
76
+ # @private
77
+ NotPreparedError = Class.new(Error)
78
+ end
79
+ end
@@ -0,0 +1,51 @@
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 Execution
21
+ class Info
22
+ # @return [String] keyspace used for the query
23
+ attr_reader :keyspace
24
+ # @return [Cassandra::Statement] original statement
25
+ attr_reader :statement
26
+ # @return [Cassandra::Execution::Options] original execution options
27
+ attr_reader :options
28
+ # @return [Array<Cassandra::Host>] a list of attempted hosts
29
+ attr_reader :hosts
30
+ # Actual consistency used, it can differ from consistency in {Cassandra::Execution::Info#options} if a retry policy modified it.
31
+ # @return [Symbol] one of {Cassandra::CONSISTENCIES}
32
+ attr_reader :consistency
33
+ # @return [Integer] number of retries
34
+ attr_reader :retries
35
+ # Returns {Cassandra::Execution::Trace} if `trace: true` was passed to {Cassandra::Session#execute} or {Cassandra::Session#execute_async}
36
+ # @return [Cassandra::Execution::Trace, nil] a Trace if it has been enabled for request
37
+ attr_reader :trace
38
+
39
+ # @private
40
+ def initialize(keyspace, statement, options, hosts, consistency, retries, trace)
41
+ @keyspace = keyspace
42
+ @statement = statement
43
+ @options = options
44
+ @hosts = hosts
45
+ @consistency = consistency
46
+ @retries = retries
47
+ @trace = trace
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,77 @@
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 Execution
21
+ class Options
22
+ # @return [Symbol] consistency for request. Must be one of
23
+ # {Cassandra::CONSISTENCIES}
24
+ attr_reader :consistency
25
+ # @return [Symbol] consistency for request with conditional updates
26
+ # (lightweight - compare-and-set, CAS - transactions). Must be one of
27
+ # {Cassandra::SERIAL_CONSISTENCIES}
28
+ attr_reader :serial_consistency
29
+ # @return [Integer] requested page size
30
+ attr_reader :page_size
31
+ # @return [Numeric] request timeout interval
32
+ attr_reader :timeout
33
+
34
+ # @private
35
+ def initialize(options)
36
+ consistency = options[:consistency]
37
+ page_size = options[:page_size]
38
+ trace = options[:trace]
39
+ timeout = options[:timeout]
40
+ serial_consistency = options[:serial_consistency]
41
+
42
+ raise ::ArgumentError, ":consistency must be one of #{CONSISTENCIES.inspect}, #{consistency.inspect} given" unless CONSISTENCIES.include?(consistency)
43
+ raise ::ArgumentError, ":serial_consistency must be one of #{SERIAL_CONSISTENCIES.inspect}, #{serial_consistency.inspect} given" if serial_consistency && !SERIAL_CONSISTENCIES.include?(serial_consistency)
44
+
45
+ page_size = page_size && Integer(page_size)
46
+ timeout = timeout && Integer(timeout)
47
+
48
+ @consistency = consistency
49
+ @page_size = page_size
50
+ @trace = !!trace
51
+ @timeout = timeout
52
+ @serial_consistency = serial_consistency
53
+ end
54
+
55
+ # @return [Boolean] whether request tracing was enabled
56
+ def trace?
57
+ @trace
58
+ end
59
+
60
+ # @private
61
+ def override(options)
62
+ Options.new(to_h.merge!(options))
63
+ end
64
+
65
+ # @private
66
+ def to_h
67
+ {
68
+ :consistency => @consistency,
69
+ :page_size => @page_size,
70
+ :trace => @trace,
71
+ :timeout => @timeout,
72
+ :serial_consistency => @serial_consistency
73
+ }
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,152 @@
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 Execution
21
+ class Trace
22
+ class Event
23
+ # @return [Cassandra::Uuid] event uuid
24
+ attr_reader :id
25
+
26
+ # @return [String] description of activity
27
+ attr_reader :activity
28
+
29
+ attr_reader :source
30
+ attr_reader :source_elapsed
31
+ attr_reader :thread
32
+
33
+ # @private
34
+ def initialize(id, activity, source, source_elapsed, thread)
35
+ @id = id
36
+ @activity = activity
37
+ @source = source
38
+ @source_elapsed = source_elapsed
39
+ @thread = thread
40
+ end
41
+
42
+ def ==(other)
43
+ other == @id
44
+ end
45
+
46
+ alias :eql? :==
47
+ end
48
+
49
+ include MonitorMixin
50
+
51
+ # @return [Cassandra::Uuid] trace id
52
+ attr_reader :id
53
+
54
+ # @private
55
+ def initialize(id, client)
56
+ @id = id
57
+ @client = client
58
+
59
+ mon_initialize
60
+ end
61
+
62
+ # Returns the ip of coordinator node. Typically the same as {Cassandra::Execution::Info#hosts}`.last`
63
+ #
64
+ # @return [IPAddr] ip of the coordinator node
65
+ def coordinator
66
+ load unless @coordinator
67
+
68
+ @coordinator
69
+ end
70
+
71
+ def duration
72
+ load unless @duration
73
+
74
+ @duration
75
+ end
76
+
77
+ def parameters
78
+ load unless @parameters
79
+
80
+ @parameters
81
+ end
82
+
83
+ def request
84
+ load unless @request
85
+
86
+ @request
87
+ end
88
+
89
+ def started_at
90
+ load unless @started_at
91
+
92
+ @started_at
93
+ end
94
+
95
+ # Returns all trace events
96
+ #
97
+ # @return [Array<Cassandra::Execution::Trace::Event>] events
98
+ def events
99
+ load_events unless @events
100
+
101
+ @events
102
+ end
103
+
104
+ def inspect
105
+ "#<#{self.class.name}:0x#{self.object_id.to_s(16)} @id=#{@id.inspect}>"
106
+ end
107
+
108
+ private
109
+
110
+ # @private
111
+ SELECT_SESSION = "SELECT * FROM system_traces.sessions WHERE session_id = ?"
112
+ # @private
113
+ SELECT_EVENTS = "SELECT * FROM system_traces.events WHERE session_id = ?"
114
+
115
+ # @private
116
+ def load
117
+ synchronize do
118
+ return if @loaded
119
+
120
+ data = @client.query(Statements::Simple.new(SELECT_SESSION, @id), VOID_OPTIONS).get.first
121
+ raise ::RuntimeError, "unable to load trace #{@id}" if data.nil?
122
+
123
+ @coordinator = data['coordinator']
124
+ @duration = data['duration']
125
+ @parameters = data['parameters']
126
+ @request = data['request']
127
+ @started_at = data['started_at']
128
+ @loaded = true
129
+ end
130
+
131
+ nil
132
+ end
133
+
134
+ # @private
135
+ def load_events
136
+ synchronize do
137
+ return if @loaded_events
138
+
139
+ @events = []
140
+
141
+ @client.query(Statements::Simple.new(SELECT_EVENTS, @id), VOID_OPTIONS).get.each do |row|
142
+ @events << Event.new(row['event_id'], row['activity'], row['source'], row['source_elapsed'], row['thread'])
143
+ end
144
+
145
+ @events.freeze
146
+
147
+ @loaded_events = true
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,675 @@
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 Future represents a result of asynchronous execution. It can be used to
21
+ # block until a value is available or an error has happened, or register a
22
+ # listener to be notified whenever the execution is complete.
23
+ class Future
24
+ # a Future listener to be passed to {Cassandra::Future#add_listener}
25
+ #
26
+ # @note Listener methods can be called from application if a future has
27
+ # been resolved or failed by the time the listener is registered; or from
28
+ # background thread if it is resolved/failed after the listener has been
29
+ # registered.
30
+ #
31
+ # @abstract Actual listeners passed to {Cassandra::Future#add_listener} don't
32
+ # need to extend this class as long as they implement `#success` and
33
+ # `#failure` methods
34
+ class Listener
35
+ # @param value [Object] actual value the future has been resolved with
36
+ # @return [void]
37
+ def success(value)
38
+ end
39
+
40
+ # @param error [Exception] an exception used to fail the future
41
+ # @return [void]
42
+ def failure(error)
43
+ end
44
+ end
45
+
46
+ # @private
47
+ class Error < Future
48
+ def initialize(error)
49
+ raise ::ArgumentError, "error must be an exception, #{error.inspect} given" unless error.is_a?(::Exception)
50
+
51
+ @error = error
52
+ end
53
+
54
+ def get
55
+ raise(@error, @error.message, @error.backtrace)
56
+ end
57
+
58
+ def on_success
59
+ raise ::ArgumentError, "no block given" unless block_given?
60
+ self
61
+ end
62
+
63
+ def on_failure
64
+ raise ::ArgumentError, "no block given" unless block_given?
65
+ yield(@error) rescue nil
66
+ self
67
+ end
68
+
69
+ def on_complete
70
+ raise ::ArgumentError, "no block given" unless block_given?
71
+ yield(nil, @error) rescue nil
72
+ self
73
+ end
74
+
75
+ def add_listener(listener)
76
+ unless (listener.respond_to?(:success) && listener.respond_to?(:failure))
77
+ raise ::ArgumentError, "listener must respond to both #success and #failure"
78
+ end
79
+
80
+ listener.failure(@error) rescue nil
81
+ self
82
+ end
83
+
84
+ def join
85
+ self
86
+ end
87
+
88
+ def then
89
+ raise ::ArgumentError, "no block given" unless block_given?
90
+ self
91
+ end
92
+
93
+ def fallback
94
+ raise ::ArgumentError, "no block given" unless block_given?
95
+
96
+ begin
97
+ result = yield(@error)
98
+ result = Value.new(result) unless result.is_a?(Future)
99
+ result
100
+ rescue => e
101
+ Error.new(e)
102
+ end
103
+ end
104
+ end
105
+
106
+ # @private
107
+ class Value < Future
108
+ def initialize(value)
109
+ @value = value
110
+ end
111
+
112
+ def get
113
+ @value
114
+ end
115
+
116
+ def on_success
117
+ raise ::ArgumentError, "no block given" unless block_given?
118
+ yield(@value) rescue nil
119
+ self
120
+ end
121
+
122
+ def on_failure
123
+ raise ::ArgumentError, "no block given" unless block_given?
124
+ self
125
+ end
126
+
127
+ def on_complete
128
+ raise ::ArgumentError, "no block given" unless block_given?
129
+ yield(@value, nil) rescue nil
130
+ self
131
+ end
132
+
133
+ def add_listener(listener)
134
+ unless (listener.respond_to?(:success) && listener.respond_to?(:failure))
135
+ raise ::ArgumentError, "listener must respond to both #success and #failure"
136
+ end
137
+
138
+ listener.success(@value) rescue nil
139
+ self
140
+ end
141
+
142
+ def join
143
+ self
144
+ end
145
+
146
+ def then
147
+ raise ::ArgumentError, "no block given" unless block_given?
148
+
149
+ begin
150
+ result = yield(@value)
151
+ result = Value.new(result) unless result.is_a?(Future)
152
+ result
153
+ rescue => e
154
+ Error.new(e)
155
+ end
156
+ end
157
+
158
+ def fallback
159
+ raise ::ArgumentError, "no block given" unless block_given?
160
+ self
161
+ end
162
+ end
163
+
164
+ # Returns a future resolved to a given value
165
+ # @param value [Object] value for the future
166
+ # @return [Cassandra::Future<Object>] a future value
167
+ def self.value(value)
168
+ Value.new(value)
169
+ end
170
+
171
+ # Returns a future resolved to a given error
172
+ # @param error [Exception] error for the future
173
+ # @return [Cassandra::Future<Exception>] a future error
174
+ def self.error(error)
175
+ Error.new(error)
176
+ end
177
+
178
+ # Returns a future that resolves with values of all futures
179
+ # @overload all(*futures)
180
+ # @param *futures [Cassandra::Future] futures to combine
181
+ # @return [Cassandra::Future<Array<Object>>] a combined future
182
+ # @overload all(futures)
183
+ # @param futures [Enumerable<Cassandra::Future>] list of futures to
184
+ # combine
185
+ # @return [Cassandra::Future<Array<Object>>] a combined future
186
+ def self.all(*futures)
187
+ futures = Array(futures.first) if futures.one?
188
+ monitor = Monitor.new
189
+ promise = Promise.new
190
+ remaining = futures.length
191
+ values = Array.new(length)
192
+
193
+ futures.each_with_index do |future, i|
194
+ future.on_complete do |v, e|
195
+ if e
196
+ promise.break(e)
197
+ else
198
+ done = false
199
+ monitor.synchronize do
200
+ remaining -= 1
201
+ done = (remaining == 0)
202
+ values[i] = v
203
+ end
204
+ promise.fulfill(values) if done
205
+ end
206
+ end
207
+ end
208
+
209
+ promise.future
210
+ end
211
+
212
+ # @private
213
+ # Returns a new promise instance
214
+ def self.promise
215
+ Promise.new
216
+ end
217
+
218
+ # @private
219
+ def initialize(signal)
220
+ @signal = signal
221
+ end
222
+
223
+ # Returns future value or raises future error
224
+ # @note This method blocks until a future is resolved
225
+ # @raise [Exception] error used to resolve this future if any
226
+ # @return [Object] value used to resolve this future if any
227
+ def get
228
+ @signal.get
229
+ end
230
+
231
+ # Block until the future has been resolved
232
+ # @note This method blocks until a future is resolved
233
+ # @note This method won't raise any errors or return anything but the
234
+ # future itself
235
+ # @return [self]
236
+ def join
237
+ @signal.join
238
+ self
239
+ end
240
+
241
+ # Run block when future resolves to a value
242
+ # @note The block can be called synchronously from current thread if the
243
+ # future has already been resolved, or, asynchronously, from background
244
+ # thread upon resolution.
245
+ # @yieldparam value [Object] a value
246
+ # @raise [ArgumentError] if no block given
247
+ # @return [self]
248
+ def on_success(&block)
249
+ raise ::ArgumentError, "no block given" unless block_given?
250
+ @signal.on_success(&block)
251
+ self
252
+ end
253
+
254
+ # Run block when future resolves to error
255
+ # @note The block can be called synchronously from current thread if the
256
+ # future has already been resolved, or, asynchronously, from background
257
+ # thread upon resolution.
258
+ # @yieldparam error [Exception] an error
259
+ # @raise [ArgumentError] if no block given
260
+ # @return [self]
261
+ def on_failure(&block)
262
+ raise ::ArgumentError, "no block given" unless block_given?
263
+ @signal.on_failure(&block)
264
+ self
265
+ end
266
+
267
+ # Run block when future resolves. The block will always be called with 2
268
+ # arguments - value and error. In case a future resolves to an error, the
269
+ # error argument will be non-nil.
270
+ # @note The block can be called synchronously from current thread if the
271
+ # future has already been resolved, or, asynchronously, from background
272
+ # thread upon resolution.
273
+ # @yieldparam value [Object, nil] a value or nil
274
+ # @yieldparam error [Exception, nil] an error or nil
275
+ # @raise [ArgumentError] if no block given
276
+ # @return [self]
277
+ def on_complete(&block)
278
+ raise ::ArgumentError, "no block given" unless block_given?
279
+ @signal.on_complete(&block)
280
+ self
281
+ end
282
+
283
+ # Add future listener
284
+ # @note The listener can be notified synchronously, from current thread, if
285
+ # the future has already been resolved, or, asynchronously, from
286
+ # background thread upon resolution.
287
+ # @note that provided listener doesn't have to extend
288
+ # {Cassandra::Future::Listener}, only conform to the same interface
289
+ # @param listener [Cassandra::Future::Listener] an object that responds to
290
+ # `#success` and `#failure`
291
+ # @return [self]
292
+ def add_listener(listener)
293
+ unless (listener.respond_to?(:success) && listener.respond_to?(:failure))
294
+ raise ::ArgumentError, "listener must respond to both #success and #failure"
295
+ end
296
+
297
+ @signal.add_listener(listener)
298
+ self
299
+ end
300
+
301
+ # Returns a new future that will resolve to the result of the block.
302
+ # Besides regular values, block can return other futures, which will be
303
+ # transparently unwrapped before resolving the future from this method.
304
+ #
305
+ # @example Block returns a value
306
+ # future_users = session.execute_async('SELECT * FROM users WHERE user_name = ?', 'Sam')
307
+ # future_user = future_users.then {|users| users.first}
308
+ #
309
+ # @example Block returns a future
310
+ # future_statement = session.prepare_async('SELECT * FROM users WHERE user_name = ?')
311
+ # future_users = future_statement.then {|statement| session.execute_async(statement, 'Sam')}
312
+ #
313
+ # @note The block can be called synchronously from current thread if the
314
+ # future has already been resolved, or, asynchronously, from background
315
+ # thread upon resolution.
316
+ # @yieldparam value [Object] a value
317
+ # @yieldreturn [Cassandra::Future, Object] a future or a value to be
318
+ # wrapped in a future
319
+ # @raise [ArgumentError] if no block given
320
+ # @return [Cassandra::Future] a new future
321
+ def then(&block)
322
+ raise ::ArgumentError, "no block given" unless block_given?
323
+ @signal.then(&block)
324
+ end
325
+
326
+ # Returns a new future that will resolve to the result of the block in case
327
+ # of an error. Besides regular values, block can return other futures,
328
+ # which will be transparently unwrapped before resolving the future from
329
+ # this method.
330
+ #
331
+ # @example Recovering from errors
332
+ # future_error = session.execute_async('SELECT * FROM invalid-table')
333
+ # future = future_error.fallback {|error| "Execution failed with #{error.class.name}: #{error.message}"}
334
+ #
335
+ # @example Executing something else on error
336
+ # future_error = session.execute_async('SELECT * FROM invalid-table')
337
+ # future = future_error.fallback {|e| session.execute_async('SELECT * FROM another-table')}
338
+ #
339
+ # @note The block can be called synchronously from current thread if the
340
+ # future has already been resolved, or, asynchronously, from background
341
+ # thread upon resolution.
342
+ # @yieldparam error [Exception] an error
343
+ # @yieldreturn [Cassandra::Future, Object] a future or a value to be
344
+ # wrapped in a future
345
+ # @raise [ArgumentError] if no block given
346
+ # @return [Cassandra::Future] a new future
347
+ def fallback(&block)
348
+ raise ::ArgumentError, "no block given" unless block_given?
349
+ @signal.fallback(&block)
350
+ end
351
+ end
352
+
353
+ # @private
354
+ class Promise
355
+ # @private
356
+ class Signal
357
+ # @private
358
+ module Listeners
359
+ class Success < Future::Listener
360
+ def initialize(&block)
361
+ @block = block
362
+ end
363
+
364
+ def success(value)
365
+ @block.call(value)
366
+ end
367
+
368
+ def failure(error)
369
+ nil
370
+ end
371
+ end
372
+
373
+ class Failure < Future::Listener
374
+ def initialize(&block)
375
+ @block = block
376
+ end
377
+
378
+ def success(value)
379
+ nil
380
+ end
381
+
382
+ def failure(error)
383
+ @block.call(error)
384
+ end
385
+ end
386
+
387
+ class Complete < Future::Listener
388
+ def initialize(&block)
389
+ @block = block
390
+ end
391
+
392
+ def success(value)
393
+ @block.call(nil, value)
394
+ end
395
+
396
+ def failure(error)
397
+ @block.call(error, nil)
398
+ end
399
+ end
400
+
401
+ class Then < Future::Listener
402
+ def initialize(promise, &block)
403
+ @promise = promise
404
+ @block = block
405
+ end
406
+
407
+ def success(value)
408
+ result = @block.call(value)
409
+
410
+ if result.is_a?(Future)
411
+ @promise.observe(result)
412
+ else
413
+ @promise.fulfill(result)
414
+ end
415
+ rescue => e
416
+ @promise.break(e)
417
+ ensure
418
+ @promise = @block = nil
419
+ end
420
+
421
+ def failure(error)
422
+ @promise.break(error)
423
+ ensure
424
+ @promise = @block = nil
425
+ end
426
+ end
427
+
428
+ class Fallback < Future::Listener
429
+ def initialize(promise, &block)
430
+ @promise = promise
431
+ @block = block
432
+ end
433
+
434
+ def success(value)
435
+ @promise.fulfill(value)
436
+ ensure
437
+ @promise = @block = nil
438
+ end
439
+
440
+ def failure(error)
441
+ result = @block.call(error)
442
+
443
+ if result.is_a?(Future)
444
+ @promise.observe(result)
445
+ else
446
+ @promise.fulfill(result)
447
+ end
448
+ rescue => e
449
+ @promise.break(e)
450
+ ensure
451
+ @promise = @block = nil
452
+ end
453
+ end
454
+ end
455
+
456
+ include MonitorMixin
457
+
458
+ def initialize
459
+ mon_initialize
460
+
461
+ @cond = new_cond
462
+ @state = :pending
463
+ @waiting = 0
464
+ @error = nil
465
+ @value = nil
466
+ @listeners = []
467
+ end
468
+
469
+ def failure(error)
470
+ unless error.is_a?(::Exception)
471
+ raise ::ArgumentError, "error must be an exception, #{error.inspect} given"
472
+ end
473
+
474
+ return unless @state == :pending
475
+
476
+ listeners = nil
477
+
478
+ synchronize do
479
+ return unless @state == :pending
480
+
481
+ @error = error
482
+ @state = :broken
483
+
484
+ listeners, @listeners = @listeners, nil
485
+ end
486
+
487
+ listeners.each do |listener|
488
+ listener.failure(error) rescue nil
489
+ end
490
+
491
+ synchronize do
492
+ @cond.broadcast if @waiting > 0
493
+ end
494
+
495
+ self
496
+ end
497
+
498
+ def success(value)
499
+ return unless @state == :pending
500
+
501
+ listeners = nil
502
+
503
+ synchronize do
504
+ return unless @state == :pending
505
+
506
+ @value = value
507
+ @state = :fulfilled
508
+
509
+ listeners, @listeners = @listeners, nil
510
+ end
511
+
512
+ listeners.each do |listener|
513
+ listener.success(value) rescue nil
514
+ end
515
+
516
+ synchronize do
517
+ @cond.broadcast if @waiting > 0
518
+ end
519
+
520
+ self
521
+ end
522
+
523
+ def join
524
+ return unless @state == :pending
525
+
526
+ synchronize do
527
+ return unless @state == :pending
528
+
529
+ @waiting += 1
530
+ @cond.wait while @state == :pending
531
+ @waiting -= 1
532
+ end
533
+
534
+ nil
535
+ end
536
+
537
+ def get
538
+ join if @state == :pending
539
+
540
+ raise(@error, @error.message, @error.backtrace) if @state == :broken
541
+
542
+ @value
543
+ end
544
+
545
+ def add_listener(listener)
546
+ if @state == :pending
547
+ synchronize do
548
+ if @state == :pending
549
+ @listeners << listener
550
+
551
+ return self
552
+ end
553
+ end
554
+ end
555
+
556
+ listener.success(@value) rescue nil if @state == :fulfilled
557
+ listener.failure(@error) rescue nil if @state == :broken
558
+
559
+ self
560
+ end
561
+
562
+ def on_success(&block)
563
+ if @state == :pending
564
+ synchronize do
565
+ if @state == :pending
566
+ @listeners << Listeners::Success.new(&block)
567
+ return self
568
+ end
569
+ end
570
+ end
571
+
572
+ yield(@value) rescue nil if @state == :fulfilled
573
+
574
+ self
575
+ end
576
+
577
+ def on_failure(&block)
578
+ if @state == :pending
579
+ synchronize do
580
+ if @state == :pending
581
+ @listeners << Listeners::Failure.new(&block)
582
+ return self
583
+ end
584
+ end
585
+ end
586
+
587
+ yield(@error) rescue nil if @state == :broken
588
+
589
+ self
590
+ end
591
+
592
+ def on_complete(&block)
593
+ if @state == :pending
594
+ synchronize do
595
+ if @state == :pending
596
+ @listeners << Listeners::Complete.new(&block)
597
+ return self
598
+ end
599
+ end
600
+ end
601
+
602
+ yield(@value, @error) rescue nil
603
+
604
+ self
605
+ end
606
+
607
+ def then(&block)
608
+ if @state == :pending
609
+ synchronize do
610
+ if @state == :pending
611
+ promise = Promise.new
612
+ listener = Listeners::Then.new(promise, &block)
613
+ @listeners << listener
614
+ return promise.future
615
+ end
616
+ end
617
+ end
618
+
619
+ return Future::Error.new(@error) if @state == :broken
620
+
621
+ begin
622
+ result = yield(@value)
623
+ result = Future::Value.new(result) unless result.is_a?(Future)
624
+ result
625
+ rescue => e
626
+ Future::Error.new(e)
627
+ end
628
+ end
629
+
630
+ def fallback(&block)
631
+ if @state == :pending
632
+ synchronize do
633
+ if @state == :pending
634
+ promise = Promise.new
635
+ listener = Listeners::Fallback.new(promise, &block)
636
+ @listeners << listener
637
+ return promise.future
638
+ end
639
+ end
640
+ end
641
+
642
+ return Future::Value.new(@value) if @state == :fulfilled
643
+
644
+ begin
645
+ result = yield(@error)
646
+ result = Future::Value.new(result) unless result.is_a?(Future)
647
+ result
648
+ rescue => e
649
+ Future::Error.new(e)
650
+ end
651
+ end
652
+ end
653
+
654
+ attr_reader :future
655
+
656
+ def initialize
657
+ @signal = Signal.new
658
+ @future = Future.new(@signal)
659
+ end
660
+
661
+ def break(error)
662
+ @signal.failure(error)
663
+ self
664
+ end
665
+
666
+ def fulfill(value)
667
+ @signal.success(value)
668
+ self
669
+ end
670
+
671
+ def observe(future)
672
+ future.add_listener(@signal)
673
+ end
674
+ end
675
+ end