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,37 @@
1
+ # encoding: utf-8
2
+
3
+ #--
4
+ # Copyright 2013-2014 DataStax, Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #++
18
+
19
+ module Cassandra
20
+ module Client
21
+ # @private
22
+ class NullLogger
23
+ def close(*); end
24
+ def debug(*); end
25
+ def debug?; false end
26
+ def error(*); end
27
+ def error?; false end
28
+ def fatal(*); end
29
+ def fatal?; false end
30
+ def info(*); end
31
+ def info?; false end
32
+ def unknown(*); end
33
+ def warn(*); end
34
+ def warn?; false end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,50 @@
1
+ # encoding: utf-8
2
+
3
+ #--
4
+ # Copyright 2013-2014 DataStax, Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #++
18
+
19
+ module Cassandra
20
+ module Client
21
+ # @private
22
+ class PeerDiscovery
23
+ def initialize(seed_connections)
24
+ @seed_connections = seed_connections
25
+ @connection = seed_connections.sample
26
+ @request_runner = RequestRunner.new
27
+ end
28
+
29
+ def new_hosts
30
+ request = Protocol::QueryRequest.new('SELECT peer, data_center, host_id, rpc_address FROM system.peers', nil, nil, :one)
31
+ response = @request_runner.execute(@connection, request)
32
+ response.map do |result|
33
+ result.each_with_object([]) do |row, new_peers|
34
+ if include?(row['host_id'], row['data_center'])
35
+ rpc_address = row['rpc_address'].to_s
36
+ rpc_address = row['peer'].to_s if rpc_address == '0.0.0.0'
37
+ new_peers << rpc_address
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def include?(host_id, dc)
46
+ @seed_connections.any? { |c| c[:data_center] == dc } && @seed_connections.none? { |c| c[:host_id] == host_id }
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,314 @@
1
+ # encoding: utf-8
2
+
3
+ #--
4
+ # Copyright 2013-2014 DataStax, Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #++
18
+
19
+ module Cassandra
20
+ module Client
21
+ # A prepared statement are CQL queries that have been sent to the server
22
+ # to be precompiled, so that when executed only their ID and not the whole
23
+ # CQL string need to be sent. They support bound values, or placeholders
24
+ # for values.
25
+ #
26
+ # Using a prepared statement for any query that you execute more than once
27
+ # is highly recommended. Besides the benefit of having less network overhead,
28
+ # and less processing overhead on the server side, they don't require you
29
+ # to build CQL strings and escape special characters, or format non-character
30
+ # data such as UUIDs, different numeric types, or collections, in the
31
+ # correct way.
32
+ #
33
+ # You should only prepare a statement once and reuse the prepared statement
34
+ # object every time you want to execute that particular query. The statement
35
+ # object will make sure that it is prepared on all connections, and will
36
+ # (lazily, but transparently) make sure it is prepared on any new connections.
37
+ #
38
+ # It is an anti-pattern to prepare the same query over and over again. It is
39
+ # bad for performance, since every preparation requires a roundtrip to all
40
+ # connected servers, and because of some bookeeping that is done to support
41
+ # automatic preparation on new connections, it will lead to unnecessary
42
+ # extra memory usage. There is no performance benefit in creating multiple
43
+ # prepared statement objects for the same query.
44
+ #
45
+ # Prepared statement objects are completely thread safe and can be shared
46
+ # across all threads in your application.
47
+ #
48
+ # @see Cassandra::Client::Client#prepare
49
+ class PreparedStatement
50
+ # Metadata describing the bound values
51
+ #
52
+ # @return [ResultMetadata]
53
+ attr_reader :metadata
54
+
55
+ # Metadata about the result (i.e. rows) that is returned when executing
56
+ # this prepared statement.
57
+ #
58
+ # @return [ResultMetadata]
59
+ attr_reader :result_metadata
60
+
61
+ # Execute the prepared statement with a list of values to be bound to the
62
+ # statements parameters.
63
+ #
64
+ # The number of arguments must equal the number of bound parameters. You
65
+ # can also specify options as the last argument, or a symbol as a shortcut
66
+ # for just specifying the consistency.
67
+ #
68
+ # Because you can specify options, or not, there is an edge case where if
69
+ # the last parameter of your prepared statement is a map, and you forget
70
+ # to specify a value for your map, the options will end up being sent to
71
+ # Cassandra. Most other cases when you specify the wrong number of
72
+ # arguments should result in an `ArgumentError` or `TypeError` being
73
+ # raised.
74
+ #
75
+ # @example Preparing and executing an `INSERT` statement
76
+ # statement = client.prepare(%(INSERT INTO metrics (id, time, value) VALUES (?, NOW(), ?)))
77
+ # statement.execute(1234, 23432)
78
+ # statement.execute(2345, 34543, tracing: true)
79
+ # statement.execute(3456, 45654, consistency: :one)
80
+ #
81
+ # @example Preparing and executing a `SELECT` statement
82
+ # statement = client.prepare(%(SELECT * FROM metrics WHERE id = ? AND time > ?))
83
+ # result = statement.execute(1234, Time.now - 3600)
84
+ # result.each do |row|
85
+ # p row
86
+ # end
87
+ #
88
+ # @param args [Array] the values for the bound parameters, and an optional
89
+ # hash of options as last argument – see {Cassandra::Client::Client#execute}
90
+ # for details on which options are available.
91
+ # @raise [ArgumentError] raised when number of argument does not match
92
+ # the number of parameters needed to be bound to the statement.
93
+ # @raise [Cassandra::Errors::NotConnectedError] raised when the client is not connected
94
+ # @raise [Cassandra::Errors::IoError] raised when there is an IO error, for example
95
+ # if the server suddenly closes the connection
96
+ # @raise [Cassandra::Errors::QueryError] raised when there is an error on the server side
97
+ # @return [nil, Cassandra::Client::QueryResult, Cassandra::Client::VoidResult] Some
98
+ # queries have no result and return `nil`, but `SELECT` statements
99
+ # return an `Enumerable` of rows (see {Cassandra::Client::QueryResult}), and
100
+ # `INSERT` and `UPDATE` return a similar type
101
+ # (see {Cassandra::Client::VoidResult}).
102
+ def execute(*args)
103
+ end
104
+
105
+ # Yields a batch when called with a block. The batch is automatically
106
+ # executed at the end of the block and the result is returned.
107
+ #
108
+ # Returns a batch when called wihtout a block. The batch will remember
109
+ # the options given and merge these with any additional options given
110
+ # when {Cassandra::Client::PreparedStatementBatch#execute} is called.
111
+ #
112
+ # The batch yielded or returned by this method is not identical to the
113
+ # regular batch objects yielded or returned by {Cassandra::Client::Client#batch}.
114
+ # These prepared statement batch objects can be used only to add multiple
115
+ # executions of the same prepared statement.
116
+ #
117
+ # Please note that the batch object returned by this method _is not thread
118
+ # safe_.
119
+ #
120
+ # The type parameter can be ommitted and the options can then be given
121
+ # as first parameter.
122
+ #
123
+ # @example Executing a prepared statement in a batch
124
+ # statement = client.prepare(%(INSERT INTO metrics (id, time, value) VALUES (?, NOW(), ?)))
125
+ # statement.batch do |batch|
126
+ # batch.add(1234, 23423)
127
+ # batch.add(2346, 13)
128
+ # batch.add(2342, 2367)
129
+ # batch.add(4562, 1231)
130
+ # end
131
+ #
132
+ # @see Cassandra::Client::PreparedStatementBatch
133
+ # @see Cassandra::Client::Client#batch
134
+ # @param [Symbol] type the type of batch, must be one of `:logged`,
135
+ # `:unlogged` and `:counter`. The precise meaning of these is defined
136
+ # in the CQL specification.
137
+ # @yieldparam [Cassandra::Client::PreparedStatementBatch] batch the batch
138
+ # @return [Cassandra::Client::VoidResult, Cassandra::Client::Batch] when no block is
139
+ # given the batch is returned, when a block is given the result of
140
+ # executing the batch is returned (see {Cassandra::Client::Batch#execute}).
141
+ def batch(type=:logged, options={})
142
+ end
143
+ end
144
+
145
+ # @private
146
+ class AsynchronousPreparedStatement < PreparedStatement
147
+ # @private
148
+ def initialize(cql, execute_options_decoder, connection_manager, logger)
149
+ @cql = cql
150
+ @execute_options_decoder = execute_options_decoder
151
+ @connection_manager = connection_manager
152
+ @logger = logger
153
+ @request_runner = RequestRunner.new
154
+ end
155
+
156
+ def self.prepare(cql, execute_options_decoder, connection_manager, logger)
157
+ statement = new(cql, execute_options_decoder, connection_manager, logger)
158
+ futures = connection_manager.map do |connection|
159
+ statement.prepare(connection)
160
+ end
161
+ Ione::Future.all(*futures).map(statement)
162
+ rescue => e
163
+ Ione::Future.failed(e)
164
+ end
165
+
166
+ def execute(*args)
167
+ connection = @connection_manager.random_connection
168
+ if connection[self]
169
+ run(args, connection)
170
+ else
171
+ prepare(connection).flat_map do
172
+ run(args, connection)
173
+ end
174
+ end
175
+ rescue => e
176
+ Ione::Future.failed(e)
177
+ end
178
+
179
+ def batch(type=:logged, options=nil)
180
+ if type.is_a?(Hash)
181
+ options = type
182
+ type = :logged
183
+ end
184
+ b = AsynchronousBatch.new(type, @execute_options_decoder, @connection_manager, options)
185
+ pb = AsynchronousPreparedStatementBatch.new(self, b)
186
+ if block_given?
187
+ yield pb
188
+ pb.execute
189
+ else
190
+ pb
191
+ end
192
+ end
193
+
194
+ # @private
195
+ def prepare(connection)
196
+ prepare_request = Protocol::PrepareRequest.new(@cql)
197
+ f = @request_runner.execute(connection, prepare_request) do |response|
198
+ connection[self] = response.id
199
+ unless @raw_metadata
200
+ # NOTE: this is not thread safe, but the worst that could happen
201
+ # is that we assign the same data multiple times
202
+ @raw_metadata = response.metadata
203
+ @metadata = ResultMetadata.new(@raw_metadata)
204
+ @raw_result_metadata = response.result_metadata
205
+ if @raw_result_metadata
206
+ @result_metadata = ResultMetadata.new(@raw_result_metadata)
207
+ end
208
+ end
209
+ hex_id = response.id.each_byte.map { |x| x.to_s(16).rjust(2, '0') }.join('')
210
+ @logger.debug('Statement %s prepared on node %s (%s:%d)' % [hex_id, connection[:host_id].to_s, connection.host, connection.port])
211
+ end
212
+ f.map(self)
213
+ end
214
+
215
+ # @private
216
+ def prepared?(connection)
217
+ !!connection[self]
218
+ end
219
+
220
+ # @private
221
+ def add_to_batch(batch, connection, bound_args)
222
+ statement_id = connection[self]
223
+ unless statement_id
224
+ raise Errors::NotPreparedError
225
+ end
226
+ unless bound_args.size == @raw_metadata.size
227
+ raise ArgumentError, "Expected #{@raw_metadata.size} arguments, got #{bound_args.size}"
228
+ end
229
+ batch.add_prepared(statement_id, @raw_metadata, bound_args)
230
+ end
231
+
232
+ private
233
+
234
+ def run(args, connection)
235
+ bound_args = args.shift(@raw_metadata.size)
236
+ unless bound_args.size == @raw_metadata.size && args.size <= 1
237
+ raise ArgumentError, "Expected #{@raw_metadata.size} arguments, got #{bound_args.size}"
238
+ end
239
+ options = @execute_options_decoder.decode_options(args.last)
240
+ statement_id = connection[self]
241
+ request_metadata = @raw_result_metadata.nil?
242
+ request = Protocol::ExecuteRequest.new(statement_id, @raw_metadata, bound_args, request_metadata, options[:consistency], options[:serial_consistency], options[:page_size], options[:paging_state], options[:trace])
243
+ f = @request_runner.execute(connection, request, options[:timeout], @raw_result_metadata)
244
+ if options.include?(:page_size)
245
+ f = f.map { |result| AsynchronousPreparedPagedQueryResult.new(self, request, result, options) }
246
+ end
247
+ f
248
+ end
249
+ end
250
+
251
+ # @private
252
+ class SynchronousPreparedStatement < PreparedStatement
253
+ include SynchronousBacktrace
254
+
255
+ def initialize(async_statement)
256
+ @async_statement = async_statement
257
+ @metadata = async_statement.metadata
258
+ @result_metadata = async_statement.result_metadata
259
+ end
260
+
261
+ def execute(*args)
262
+ synchronous_backtrace do
263
+ result = @async_statement.execute(*args).value
264
+ result = SynchronousPagedQueryResult.new(result) if result.is_a?(PagedQueryResult)
265
+ result
266
+ end
267
+ end
268
+
269
+ def batch(type=:logged, options=nil, &block)
270
+ if block_given?
271
+ synchronous_backtrace { @async_statement.batch(type, options, &block).value }
272
+ else
273
+ SynchronousPreparedStatementBatch.new(@async_statement.batch(type, options))
274
+ end
275
+ end
276
+
277
+ def pipeline
278
+ pl = Pipeline.new(@async_statement)
279
+ yield pl
280
+ synchronous_backtrace { pl.value }
281
+ end
282
+
283
+ def async
284
+ @async_statement
285
+ end
286
+
287
+ # @private
288
+ def prepared?(connection)
289
+ @async_statement.prepared?(connection)
290
+ end
291
+
292
+ # @private
293
+ def add_to_batch(batch, connection, bound_arguments)
294
+ @async_statement.add_to_batch(batch, connection, bound_arguments)
295
+ end
296
+ end
297
+
298
+ # @private
299
+ class Pipeline
300
+ def initialize(async_statement)
301
+ @async_statement = async_statement
302
+ @futures = []
303
+ end
304
+
305
+ def execute(*args)
306
+ @futures << @async_statement.execute(*args)
307
+ end
308
+
309
+ def value
310
+ Ione::Future.all(*@futures).value
311
+ end
312
+ end
313
+ end
314
+ end
@@ -0,0 +1,230 @@
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
+ # Query results encapsulate the rows returned by a query.
22
+ #
23
+ # In addition to containing the rows it contains metadata about the data
24
+ # types of the columns of the rows, and it knows the ID of the trace,
25
+ # if tracing was requested for the query.
26
+ #
27
+ # When paging over a big result you can use {#last_page?} to find out if the
28
+ # page is the last, or {#next_page} to retrieve the next page.
29
+ #
30
+ # `QueryResult` is an `Enumerable` so it can be mapped, filtered, reduced, etc.
31
+ class QueryResult
32
+ include Enumerable
33
+
34
+ # @return [ResultMetadata]
35
+ attr_reader :metadata
36
+
37
+ # The ID of the query trace associated with the query, if any.
38
+ #
39
+ # @return [Cassandra::Uuid]
40
+ attr_reader :trace_id
41
+
42
+ # @private
43
+ attr_reader :paging_state
44
+
45
+ # @private
46
+ def initialize(metadata, rows, trace_id, paging_state)
47
+ @metadata = ResultMetadata.new(metadata)
48
+ @rows = rows
49
+ @trace_id = trace_id
50
+ @paging_state = paging_state
51
+ end
52
+
53
+ # Returns whether or not there are any rows in this result set
54
+ def empty?
55
+ @rows.empty?
56
+ end
57
+
58
+ # Returns count of underlying rows
59
+ def size
60
+ @rows.size
61
+ end
62
+ alias :length :size
63
+
64
+ # Iterates over each row in the result set.
65
+ #
66
+ # @yieldparam [Hash] row each row in the result set as a hash
67
+ # @return [Enumerable<Hash>]
68
+ def each(&block)
69
+ @rows.each(&block)
70
+ end
71
+ alias_method :each_row, :each
72
+
73
+ # Returns true when there are no more pages to load.
74
+ #
75
+ # This is only relevant when you have requested paging of the results with
76
+ # the `:page_size` option to {Cassandra::Client::Client#execute} or
77
+ # {Cassandra::Client::PreparedStatement#execute}.
78
+ #
79
+ # @see Cassandra::Client::Client#execute
80
+ def last_page?
81
+ true
82
+ end
83
+
84
+ # Returns the next page or nil when there is no next page.
85
+ #
86
+ # This is only relevant when you have requested paging of the results with
87
+ # the `:page_size` option to {Cassandra::Client::Client#execute} or
88
+ # {Cassandra::Client::PreparedStatement#execute}.
89
+ #
90
+ # @see Cassandra::Client::Client#execute
91
+ def next_page
92
+ nil
93
+ end
94
+ end
95
+
96
+ # @private
97
+ class PagedQueryResult < QueryResult
98
+ def metadata
99
+ @result.metadata
100
+ end
101
+
102
+ def trace_id
103
+ @result.trace_id
104
+ end
105
+
106
+ def paging_state
107
+ @result.paging_state
108
+ end
109
+
110
+ def empty?
111
+ @result.empty?
112
+ end
113
+
114
+ def each(&block)
115
+ @result.each(&block)
116
+ end
117
+ alias_method :each_row, :each
118
+
119
+ def last_page?
120
+ @last_page
121
+ end
122
+
123
+ def next_page
124
+ end
125
+ end
126
+
127
+ # @private
128
+ class AsynchronousPagedQueryResult < PagedQueryResult
129
+ def initialize(request, result, options)
130
+ @request = request
131
+ @result = result
132
+ @result = result
133
+ @options = options.merge(paging_state: result.paging_state)
134
+ @last_page = !result.paging_state
135
+ end
136
+ end
137
+
138
+ # @private
139
+ class AsynchronousQueryPagedQueryResult < AsynchronousPagedQueryResult
140
+ def initialize(client, request, result, options)
141
+ super(request, result, options)
142
+ @client = client
143
+ end
144
+
145
+ def next_page
146
+ return Ione::Future.resolved(nil) if last_page?
147
+ @client.execute(@request.cql, *@request.values, @options)
148
+ end
149
+ end
150
+
151
+ # @private
152
+ class AsynchronousPreparedPagedQueryResult < AsynchronousPagedQueryResult
153
+ def initialize(prepared_statement, request, result, options)
154
+ super(request, result, options)
155
+ @prepared_statement = prepared_statement
156
+ end
157
+
158
+ def next_page
159
+ return Ione::Future.resolved(nil) if last_page?
160
+ @prepared_statement.execute(*@request.values, @options)
161
+ end
162
+ end
163
+
164
+ # @private
165
+ class SynchronousPagedQueryResult < PagedQueryResult
166
+ include SynchronousBacktrace
167
+
168
+ def initialize(asynchronous_result)
169
+ @result = asynchronous_result
170
+ end
171
+
172
+ def async
173
+ @result
174
+ end
175
+
176
+ def last_page?
177
+ @result.last_page?
178
+ end
179
+
180
+ def next_page
181
+ synchronous_backtrace do
182
+ asynchronous_result = @result.next_page.value
183
+ asynchronous_result && self.class.new(asynchronous_result)
184
+ end
185
+ end
186
+ end
187
+
188
+ # @private
189
+ class LazyQueryResult < QueryResult
190
+ def initialize(metadata, lazy_rows, trace_id, paging_state)
191
+ super(metadata, nil, trace_id, paging_state)
192
+ @raw_metadata = metadata
193
+ @lazy_rows = lazy_rows
194
+ @lock = Mutex.new
195
+ end
196
+
197
+ def empty?
198
+ ensure_materialized
199
+ super
200
+ end
201
+
202
+ def size
203
+ ensure_materialized
204
+ super
205
+ end
206
+ alias :length :size
207
+
208
+ def each(&block)
209
+ ensure_materialized
210
+ super
211
+ end
212
+ alias_method :each_row, :each
213
+
214
+ private
215
+
216
+ def ensure_materialized
217
+ unless @rows
218
+ @lock.lock
219
+ begin
220
+ unless @rows
221
+ @rows = @lazy_rows.materialize(@raw_metadata)
222
+ end
223
+ ensure
224
+ @lock.unlock
225
+ end
226
+ end
227
+ end
228
+ end
229
+ end
230
+ end