cql-rb 1.2.2 → 2.0.0.pre0

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 (108) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +4 -0
  3. data/README.md +139 -17
  4. data/lib/cql/client.rb +237 -8
  5. data/lib/cql/client/asynchronous_client.rb +138 -54
  6. data/lib/cql/client/asynchronous_prepared_statement.rb +41 -6
  7. data/lib/cql/client/authenticators.rb +46 -0
  8. data/lib/cql/client/batch.rb +115 -0
  9. data/lib/cql/client/connector.rb +255 -0
  10. data/lib/cql/client/execute_options_decoder.rb +25 -9
  11. data/lib/cql/client/keyspace_changer.rb +5 -5
  12. data/lib/cql/client/peer_discovery.rb +33 -0
  13. data/lib/cql/client/query_result.rb +124 -1
  14. data/lib/cql/client/request_runner.rb +4 -2
  15. data/lib/cql/client/synchronous_client.rb +14 -2
  16. data/lib/cql/client/synchronous_prepared_statement.rb +19 -1
  17. data/lib/cql/future.rb +97 -50
  18. data/lib/cql/io/connection.rb +0 -1
  19. data/lib/cql/io/io_reactor.rb +1 -1
  20. data/lib/cql/protocol.rb +8 -1
  21. data/lib/cql/protocol/cql_protocol_handler.rb +2 -2
  22. data/lib/cql/protocol/decoding.rb +10 -15
  23. data/lib/cql/protocol/frame_decoder.rb +2 -1
  24. data/lib/cql/protocol/frame_encoder.rb +5 -4
  25. data/lib/cql/protocol/requests/auth_response_request.rb +31 -0
  26. data/lib/cql/protocol/requests/batch_request.rb +59 -0
  27. data/lib/cql/protocol/requests/credentials_request.rb +1 -1
  28. data/lib/cql/protocol/requests/execute_request.rb +45 -17
  29. data/lib/cql/protocol/requests/options_request.rb +1 -1
  30. data/lib/cql/protocol/requests/prepare_request.rb +1 -1
  31. data/lib/cql/protocol/requests/query_request.rb +97 -5
  32. data/lib/cql/protocol/requests/register_request.rb +1 -1
  33. data/lib/cql/protocol/requests/startup_request.rb +4 -4
  34. data/lib/cql/protocol/response.rb +2 -2
  35. data/lib/cql/protocol/responses/auth_challenge_response.rb +25 -0
  36. data/lib/cql/protocol/responses/auth_success_response.rb +25 -0
  37. data/lib/cql/protocol/responses/authenticate_response.rb +1 -1
  38. data/lib/cql/protocol/responses/detailed_error_response.rb +1 -1
  39. data/lib/cql/protocol/responses/error_response.rb +3 -2
  40. data/lib/cql/protocol/responses/event_response.rb +3 -2
  41. data/lib/cql/protocol/responses/prepared_result_response.rb +10 -6
  42. data/lib/cql/protocol/responses/raw_rows_result_response.rb +27 -0
  43. data/lib/cql/protocol/responses/ready_response.rb +1 -1
  44. data/lib/cql/protocol/responses/result_response.rb +2 -2
  45. data/lib/cql/protocol/responses/rows_result_response.rb +43 -23
  46. data/lib/cql/protocol/responses/schema_change_event_response.rb +1 -1
  47. data/lib/cql/protocol/responses/schema_change_result_response.rb +1 -1
  48. data/lib/cql/protocol/responses/set_keyspace_result_response.rb +1 -1
  49. data/lib/cql/protocol/responses/status_change_event_response.rb +1 -1
  50. data/lib/cql/protocol/responses/supported_response.rb +1 -1
  51. data/lib/cql/protocol/responses/void_result_response.rb +1 -1
  52. data/lib/cql/protocol/type_converter.rb +2 -2
  53. data/lib/cql/uuid.rb +2 -2
  54. data/lib/cql/version.rb +1 -1
  55. data/spec/cql/client/asynchronous_client_spec.rb +493 -50
  56. data/spec/cql/client/asynchronous_prepared_statement_spec.rb +193 -11
  57. data/spec/cql/client/authenticators_spec.rb +56 -0
  58. data/spec/cql/client/batch_spec.rb +277 -0
  59. data/spec/cql/client/connector_spec.rb +606 -0
  60. data/spec/cql/client/execute_options_decoder_spec.rb +95 -0
  61. data/spec/cql/client/keyspace_changer_spec.rb +8 -8
  62. data/spec/cql/client/peer_discovery_spec.rb +92 -0
  63. data/spec/cql/client/query_result_spec.rb +352 -0
  64. data/spec/cql/client/request_runner_spec.rb +31 -5
  65. data/spec/cql/client/synchronous_client_spec.rb +44 -1
  66. data/spec/cql/client/synchronous_prepared_statement_spec.rb +63 -1
  67. data/spec/cql/future_spec.rb +50 -2
  68. data/spec/cql/protocol/cql_protocol_handler_spec.rb +16 -5
  69. data/spec/cql/protocol/decoding_spec.rb +16 -6
  70. data/spec/cql/protocol/encoding_spec.rb +3 -1
  71. data/spec/cql/protocol/frame_encoder_spec.rb +99 -50
  72. data/spec/cql/protocol/requests/auth_response_request_spec.rb +62 -0
  73. data/spec/cql/protocol/requests/batch_request_spec.rb +155 -0
  74. data/spec/cql/protocol/requests/credentials_request_spec.rb +1 -1
  75. data/spec/cql/protocol/requests/execute_request_spec.rb +184 -71
  76. data/spec/cql/protocol/requests/options_request_spec.rb +1 -1
  77. data/spec/cql/protocol/requests/prepare_request_spec.rb +1 -1
  78. data/spec/cql/protocol/requests/query_request_spec.rb +255 -32
  79. data/spec/cql/protocol/requests/register_request_spec.rb +1 -1
  80. data/spec/cql/protocol/requests/startup_request_spec.rb +12 -6
  81. data/spec/cql/protocol/responses/auth_challenge_response_spec.rb +31 -0
  82. data/spec/cql/protocol/responses/auth_success_response_spec.rb +31 -0
  83. data/spec/cql/protocol/responses/authenticate_response_spec.rb +2 -1
  84. data/spec/cql/protocol/responses/detailed_error_response_spec.rb +14 -7
  85. data/spec/cql/protocol/responses/error_response_spec.rb +4 -2
  86. data/spec/cql/protocol/responses/event_response_spec.rb +7 -4
  87. data/spec/cql/protocol/responses/prepared_result_response_spec.rb +89 -34
  88. data/spec/cql/protocol/responses/raw_rows_result_response_spec.rb +66 -0
  89. data/spec/cql/protocol/responses/ready_response_spec.rb +1 -1
  90. data/spec/cql/protocol/responses/result_response_spec.rb +19 -7
  91. data/spec/cql/protocol/responses/rows_result_response_spec.rb +56 -11
  92. data/spec/cql/protocol/responses/schema_change_event_response_spec.rb +2 -1
  93. data/spec/cql/protocol/responses/schema_change_result_response_spec.rb +2 -1
  94. data/spec/cql/protocol/responses/set_keyspace_result_response_spec.rb +1 -1
  95. data/spec/cql/protocol/responses/status_change_event_response_spec.rb +2 -1
  96. data/spec/cql/protocol/responses/supported_response_spec.rb +2 -1
  97. data/spec/cql/protocol/responses/topology_change_event_response_spec.rb +2 -1
  98. data/spec/cql/protocol/responses/void_result_response_spec.rb +1 -1
  99. data/spec/cql/protocol/type_converter_spec.rb +21 -4
  100. data/spec/cql/uuid_spec.rb +10 -3
  101. data/spec/integration/client_spec.rb +251 -28
  102. data/spec/integration/protocol_spec.rb +213 -62
  103. data/spec/integration/regression_spec.rb +4 -1
  104. data/spec/integration/uuid_spec.rb +4 -1
  105. data/spec/support/fake_io_reactor.rb +5 -5
  106. metadata +36 -7
  107. data/lib/cql/client/connection_helper.rb +0 -181
  108. data/spec/cql/client/connection_helper_spec.rb +0 -429
@@ -0,0 +1,255 @@
1
+ # encoding: utf-8
2
+
3
+ module Cql
4
+ module Client
5
+ # @private
6
+ class ClusterConnector
7
+ def initialize(sequence, logger)
8
+ @sequence = sequence
9
+ @logger = logger
10
+ end
11
+
12
+ def connect_all(hosts, connections_per_node)
13
+ connections = hosts.flat_map do |host|
14
+ Array.new(connections_per_node) do
15
+ f = @sequence.connect(host)
16
+ f.on_value { |connection| register_logging(connection) }
17
+ f.recover do |error|
18
+ @logger.warn('Failed connecting to node at %s: %s' % [host, error.message])
19
+ FailedConnection.new(error, host)
20
+ end
21
+ end
22
+ end
23
+ Future.all(*connections).map do |connections|
24
+ connected_connections = connections.select(&:connected?)
25
+ if connected_connections.empty?
26
+ e = connections.first.error
27
+ if e.is_a?(Cql::QueryError) && e.code == 0x100
28
+ e = AuthenticationError.new(e.message)
29
+ end
30
+ raise e
31
+ end
32
+ connected_connections
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def register_logging(connection)
39
+ args = [connection[:host_id], connection.host, connection.port, connection[:data_center]]
40
+ @logger.info('Connected to node %s at %s:%d in data center %s' % args)
41
+ connection.on_closed do |cause|
42
+ message = 'Connection to node %s at %s:%d in data center %s closed' % args
43
+ if cause
44
+ message << (' unexpectedly: %s' % cause.message)
45
+ @logger.warn(message)
46
+ else
47
+ @logger.info(message)
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ # @private
54
+ class Connector
55
+ def initialize(steps)
56
+ @steps = steps.dup
57
+ end
58
+
59
+ def connect(host)
60
+ pending_connection = PendingConnection.new(host)
61
+ seed = Future.resolved(pending_connection)
62
+ f = @steps.reduce(seed) do |chain, step|
63
+ chain.flat_map do |pending_connection|
64
+ step.run(pending_connection)
65
+ end
66
+ end
67
+ f.map do |pending_connection|
68
+ pending_connection.connection
69
+ end
70
+ end
71
+ end
72
+
73
+ # @private
74
+ class ConnectStep
75
+ def initialize(io_reactor, port, connection_timeout, logger)
76
+ @io_reactor = io_reactor
77
+ @port = port
78
+ @connection_timeout = connection_timeout
79
+ @logger = logger
80
+ end
81
+
82
+ def run(pending_connection)
83
+ @logger.debug('Connecting to node at %s:%d' % [pending_connection.host, @port])
84
+ @io_reactor.connect(pending_connection.host, @port, @connection_timeout).map do |connection|
85
+ pending_connection.with_connection(connection)
86
+ end
87
+ end
88
+ end
89
+
90
+ # @private
91
+ class CacheOptionsStep
92
+ def run(pending_connection)
93
+ f = pending_connection.execute(Protocol::OptionsRequest.new)
94
+ f.on_value do |supported_options|
95
+ pending_connection[:cql_version] = supported_options['CQL_VERSION']
96
+ pending_connection[:compression] = supported_options['COMPRESSION']
97
+ end
98
+ f.map(pending_connection)
99
+ end
100
+ end
101
+
102
+ # @private
103
+ class InitializeStep
104
+ def initialize(cql_version, compressor, logger)
105
+ @cql_version = cql_version
106
+ @compressor = compressor
107
+ @logger = logger
108
+ end
109
+
110
+ def run(pending_connection)
111
+ compression = @compressor && @compressor.algorithm
112
+ supported_algorithms = pending_connection[:compression]
113
+ if @compressor && !supported_algorithms.include?(@compressor.algorithm)
114
+ @logger.warn(%[Compression algorithm "#{@compressor.algorithm}" not supported (server supports "#{supported_algorithms.join('", "')}")])
115
+ compression = nil
116
+ elsif @compressor
117
+ @logger.debug('Using "%s" compression' % @compressor.algorithm)
118
+ end
119
+ f = pending_connection.execute(Protocol::StartupRequest.new(@cql_version, compression))
120
+ f.map do |startup_response|
121
+ if startup_response.is_a?(AuthenticationRequired)
122
+ pending_connection.with_authentication_class(startup_response.authentication_class)
123
+ else
124
+ pending_connection
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ # @private
131
+ class SaslAuthenticationStep
132
+ def initialize(auth_provider)
133
+ @auth_provider = auth_provider
134
+ end
135
+
136
+ def run(pending_connection)
137
+ if pending_connection.authentication_class
138
+ begin
139
+ authenticator = @auth_provider && @auth_provider.create_authenticator(pending_connection.authentication_class)
140
+ if authenticator
141
+ token = authenticator.initial_response
142
+ challenge_cycle(pending_connection, authenticator, token)
143
+ elsif @auth_provider
144
+ Future.failed(AuthenticationError.new('Auth provider does not support the required authentication class "%s" and/or protocol version %d' % [pending_connection.authentication_class, @protocol_version]))
145
+ else
146
+ Future.failed(AuthenticationError.new('Server requested authentication, but no auth provider found'))
147
+ end
148
+ rescue => e
149
+ Future.failed(AuthenticationError.new('Auth provider raised an error: %s' % e.message))
150
+ end
151
+ else
152
+ Future.resolved(pending_connection)
153
+ end
154
+ end
155
+
156
+ def challenge_cycle(pending_connection, authenticator, response_token)
157
+ request = Protocol::AuthResponseRequest.new(response_token)
158
+ f = pending_connection.execute(request) { |raw_response| raw_response }
159
+ f.flat_map do |response|
160
+ case response
161
+ when Protocol::AuthChallengeResponse
162
+ token = authenticator.challenge_response(response.token)
163
+ challenge_cycle(pending_connection, authenticator, token)
164
+ when Protocol::AuthSuccessResponse
165
+ authenticator.authentication_successful(response.token)
166
+ Future.resolved(pending_connection)
167
+ else
168
+ Future.resolved(pending_connection)
169
+ end
170
+ end
171
+ end
172
+ end
173
+
174
+ # @private
175
+ class CredentialsAuthenticationStep
176
+ def initialize(credentials)
177
+ @credentials = credentials
178
+ end
179
+
180
+ def run(pending_connection)
181
+ if pending_connection.authentication_class
182
+ if @credentials
183
+ request = Protocol::CredentialsRequest.new(@credentials)
184
+ pending_connection.execute(request).map(pending_connection)
185
+ else
186
+ Future.failed(AuthenticationError.new('Server requested authentication, but no credentials provided'))
187
+ end
188
+ else
189
+ Future.resolved(pending_connection)
190
+ end
191
+ end
192
+ end
193
+
194
+ # @private
195
+ class CachePropertiesStep
196
+ def run(pending_connection)
197
+ request = Protocol::QueryRequest.new('SELECT data_center, host_id FROM system.local', nil, nil, :one)
198
+ f = pending_connection.execute(request)
199
+ f.on_value do |result|
200
+ unless result.empty?
201
+ pending_connection[:host_id] = result.first['host_id']
202
+ pending_connection[:data_center] = result.first['data_center']
203
+ end
204
+ end
205
+ f.map(pending_connection)
206
+ end
207
+ end
208
+
209
+ # @private
210
+ class PendingConnection
211
+ attr_reader :host, :connection, :authentication_class
212
+
213
+ def initialize(host, connection=nil, authentication_class=nil)
214
+ @host = host
215
+ @connection = connection
216
+ @authentication_class = authentication_class
217
+ @request_runner = RequestRunner.new
218
+ end
219
+
220
+ def with_connection(connection)
221
+ self.class.new(host, connection, @authentication_class)
222
+ end
223
+
224
+ def with_authentication_class(authentication_class)
225
+ self.class.new(host, @connection, authentication_class)
226
+ end
227
+
228
+ def [](key)
229
+ @connection[key]
230
+ end
231
+
232
+ def []=(key, value)
233
+ @connection[key] = value
234
+ end
235
+
236
+ def execute(request, &block)
237
+ @request_runner.execute(@connection, request, nil, nil, &block)
238
+ end
239
+ end
240
+
241
+ # @private
242
+ class FailedConnection
243
+ attr_reader :error, :host
244
+
245
+ def initialize(error, host)
246
+ @error = error
247
+ @host = host
248
+ end
249
+
250
+ def connected?
251
+ false
252
+ end
253
+ end
254
+ end
255
+ end
@@ -6,21 +6,37 @@ module Cql
6
6
  class ExecuteOptionsDecoder
7
7
  def initialize(default_consistency)
8
8
  @default_consistency = default_consistency
9
+ @default_options = {:consistency => @default_consistency}.freeze
9
10
  end
10
11
 
11
- def decode_options(options_or_consistency)
12
- consistency = @default_consistency
13
- timeout = nil
14
- trace = false
12
+ def decode_options(*args)
13
+ if args.empty?
14
+ @default_options
15
+ elsif args.size == 1
16
+ decode_one(args.first)
17
+ else
18
+ args.each_with_object({}) do |options_or_consistency, result|
19
+ result.merge!(decode_one(options_or_consistency))
20
+ end
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def decode_one(options_or_consistency)
27
+ return @default_options unless options_or_consistency
15
28
  case options_or_consistency
16
29
  when Symbol
17
- consistency = options_or_consistency
30
+ {:consistency => options_or_consistency}
18
31
  when Hash
19
- consistency = options_or_consistency[:consistency] || consistency
20
- timeout = options_or_consistency[:timeout]
21
- trace = options_or_consistency[:trace]
32
+ if options_or_consistency.include?(:consistency)
33
+ options_or_consistency
34
+ else
35
+ options = options_or_consistency.dup
36
+ options[:consistency] = @default_consistency
37
+ options
38
+ end
22
39
  end
23
- return consistency, timeout, trace
24
40
  end
25
41
  end
26
42
  end
@@ -6,15 +6,15 @@ module Cql
6
6
  class KeyspaceChanger
7
7
  KEYSPACE_NAME_PATTERN = /^\w[\w\d_]*$|^"\w[\w\d_]*"$/
8
8
 
9
- def initialize
10
- @request_runner = RequestRunner.new
9
+ def initialize(request_runner=RequestRunner.new)
10
+ @request_runner = request_runner
11
11
  end
12
12
 
13
- def use_keyspace(keyspace, connection)
13
+ def use_keyspace(connection, keyspace)
14
14
  return Future.resolved(connection) unless keyspace
15
15
  return Future.failed(InvalidKeyspaceNameError.new(%("#{keyspace}" is not a valid keyspace name))) unless valid_keyspace_name?(keyspace)
16
- request = Protocol::QueryRequest.new("USE #{keyspace}", :one)
17
- @request_runner.execute(connection, request).map { connection }
16
+ request = Protocol::QueryRequest.new("USE #{keyspace}", nil, nil, :one)
17
+ @request_runner.execute(connection, request).map(connection)
18
18
  end
19
19
 
20
20
  private
@@ -0,0 +1,33 @@
1
+ # encoding: utf-8
2
+
3
+ module Cql
4
+ module Client
5
+ class PeerDiscovery
6
+ def initialize(seed_connections)
7
+ @seed_connections = seed_connections
8
+ @connection = seed_connections.sample
9
+ @request_runner = RequestRunner.new
10
+ end
11
+
12
+ def new_hosts
13
+ request = Protocol::QueryRequest.new('SELECT peer, data_center, host_id, rpc_address FROM system.peers', nil, nil, :one)
14
+ response = @request_runner.execute(@connection, request)
15
+ response.map do |result|
16
+ result.each_with_object([]) do |row, new_peers|
17
+ if include?(row['host_id'], row['data_center'])
18
+ rpc_address = row['rpc_address'].to_s
19
+ rpc_address = row['peer'].to_s if rpc_address == '0.0.0.0'
20
+ new_peers << rpc_address
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def include?(host_id, dc)
29
+ @seed_connections.any? { |c| c[:data_center] == dc } && @seed_connections.none? { |c| c[:host_id] == host_id }
30
+ end
31
+ end
32
+ end
33
+ end
@@ -14,10 +14,14 @@ module Cql
14
14
  attr_reader :trace_id
15
15
 
16
16
  # @private
17
- def initialize(metadata, rows, trace_id)
17
+ attr_reader :paging_state
18
+
19
+ # @private
20
+ def initialize(metadata, rows, trace_id, paging_state)
18
21
  @metadata = ResultMetadata.new(metadata)
19
22
  @rows = rows
20
23
  @trace_id = trace_id
24
+ @paging_state = paging_state
21
25
  end
22
26
 
23
27
  # Returns whether or not there are any rows in this result set
@@ -34,5 +38,124 @@ module Cql
34
38
  end
35
39
  alias_method :each_row, :each
36
40
  end
41
+
42
+ class PagedQueryResult < QueryResult
43
+ def metadata
44
+ @result.metadata
45
+ end
46
+
47
+ def trace_id
48
+ @result.trace_id
49
+ end
50
+
51
+ def paging_state
52
+ @result.paging_state
53
+ end
54
+
55
+ def empty?
56
+ @result.empty?
57
+ end
58
+
59
+ def each(&block)
60
+ @result.each(&block)
61
+ end
62
+ alias_method :each_row, :each
63
+
64
+ def last_page?
65
+ @last_page
66
+ end
67
+
68
+ def next_page
69
+ end
70
+ end
71
+
72
+ # @private
73
+ class AsynchronousPagedQueryResult < PagedQueryResult
74
+ def initialize(request, result, options)
75
+ @request = request
76
+ @result = result
77
+ @result = result
78
+ @options = options.merge(paging_state: result.paging_state)
79
+ @last_page = !result.paging_state
80
+ end
81
+ end
82
+
83
+ # @private
84
+ class AsynchronousQueryPagedQueryResult < AsynchronousPagedQueryResult
85
+ def initialize(client, request, result, options)
86
+ super(request, result, options)
87
+ @client = client
88
+ end
89
+
90
+ def next_page
91
+ @client.execute(@request.cql, *@request.values, @options)
92
+ end
93
+ end
94
+
95
+ # @private
96
+ class AsynchronousPreparedPagedQueryResult < AsynchronousPagedQueryResult
97
+ def initialize(prepared_statement, request, result, options)
98
+ super(request, result, options)
99
+ @prepared_statement = prepared_statement
100
+ end
101
+
102
+ def next_page
103
+ @prepared_statement.execute(*@request.values, @options)
104
+ end
105
+ end
106
+
107
+ # @private
108
+ class SynchronousPagedQueryResult < PagedQueryResult
109
+ include SynchronousBacktrace
110
+
111
+ def initialize(asynchronous_result)
112
+ @result = asynchronous_result
113
+ end
114
+
115
+ def async
116
+ @result
117
+ end
118
+
119
+ def last_page?
120
+ @result.last_page?
121
+ end
122
+
123
+ def next_page
124
+ synchronous_backtrace { self.class.new(@result.next_page.value) }
125
+ end
126
+ end
127
+
128
+ # @private
129
+ class LazyQueryResult < QueryResult
130
+ def initialize(metadata, lazy_rows, trace_id, paging_state)
131
+ super(metadata, nil, trace_id, paging_state)
132
+ @raw_metadata = metadata
133
+ @lazy_rows = lazy_rows
134
+ @lock = Mutex.new
135
+ end
136
+
137
+ def empty?
138
+ ensure_materialized
139
+ super
140
+ end
141
+
142
+ def each(&block)
143
+ ensure_materialized
144
+ super
145
+ end
146
+ alias_method :each_row, :each
147
+
148
+ private
149
+
150
+ def ensure_materialized
151
+ unless @rows
152
+ @lock.synchronize do
153
+ unless @rows
154
+ @rows = @lazy_rows.materialize(@raw_metadata)
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
37
160
  end
38
161
  end