cql-rb 1.2.2 → 2.0.0.pre0

Sign up to get free protection for your applications and to get access to all the features.
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