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
@@ -5,7 +5,7 @@ module Cql
5
5
  class AuthenticateResponse < Response
6
6
  attr_reader :authentication_class
7
7
 
8
- def self.decode!(buffer, trace_id=nil)
8
+ def self.decode!(protocol_version, buffer, length, trace_id=nil)
9
9
  new(read_string!(buffer))
10
10
  end
11
11
 
@@ -10,7 +10,7 @@ module Cql
10
10
  @details = details
11
11
  end
12
12
 
13
- def self.decode!(code, message, buffer, trace_id=nil)
13
+ def self.decode!(code, message, protocol_version, buffer, length, trace_id=nil)
14
14
  details = {}
15
15
  case code
16
16
  when 0x1000 # unavailable
@@ -9,12 +9,13 @@ module Cql
9
9
  @code, @message = args
10
10
  end
11
11
 
12
- def self.decode!(buffer, trace_id=nil)
12
+ def self.decode!(protocol_version, buffer, length, trace_id=nil)
13
13
  code = read_int!(buffer)
14
14
  message = read_string!(buffer)
15
15
  case code
16
16
  when 0x1000, 0x1100, 0x1200, 0x2400, 0x2500
17
- DetailedErrorResponse.decode!(code, message, buffer)
17
+ new_length = length - 4 - 4 - message.bytesize
18
+ DetailedErrorResponse.decode!(code, message, protocol_version, buffer, new_length)
18
19
  else
19
20
  new(code, message)
20
21
  end
@@ -3,11 +3,12 @@
3
3
  module Cql
4
4
  module Protocol
5
5
  class EventResponse < ResultResponse
6
- def self.decode!(buffer, trace_id=nil)
6
+ def self.decode!(protocol_version, buffer, length, trace_id=nil)
7
7
  type = read_string!(buffer)
8
8
  impl = EVENT_TYPES[type]
9
9
  raise UnsupportedEventTypeError, %(Unsupported event type: "#{type}") unless impl
10
- impl.decode!(buffer, trace_id)
10
+ new_length = length - 4 - type.bytesize
11
+ impl.decode!(protocol_version, buffer, new_length, trace_id)
11
12
  end
12
13
 
13
14
  private
@@ -3,17 +3,21 @@
3
3
  module Cql
4
4
  module Protocol
5
5
  class PreparedResultResponse < ResultResponse
6
- attr_reader :id, :metadata
6
+ attr_reader :id, :metadata, :result_metadata
7
7
 
8
- def initialize(id, metadata, trace_id)
8
+ def initialize(id, metadata, result_metadata, trace_id)
9
9
  super(trace_id)
10
- @id, @metadata = id, metadata
10
+ @id, @metadata, @result_metadata = id, metadata, result_metadata
11
11
  end
12
12
 
13
- def self.decode!(buffer, trace_id=nil)
13
+ def self.decode!(protocol_version, buffer, length, trace_id=nil)
14
14
  id = read_short_bytes!(buffer)
15
- metadata = RowsResultResponse.read_metadata!(buffer)
16
- new(id, metadata, trace_id)
15
+ metadata, _ = RowsResultResponse.read_metadata!(protocol_version, buffer)
16
+ result_metadata = nil
17
+ if protocol_version > 1
18
+ result_metadata, _, _ = RowsResultResponse.read_metadata!(protocol_version, buffer)
19
+ end
20
+ new(id, metadata, result_metadata, trace_id)
17
21
  end
18
22
 
19
23
  def eql?(other)
@@ -0,0 +1,27 @@
1
+ # encoding: utf-8
2
+
3
+ module Cql
4
+ module Protocol
5
+ class RawRowsResultResponse < RowsResultResponse
6
+ def initialize(protocol_version, raw_rows, paging_state, trace_id)
7
+ super(nil, nil, paging_state, trace_id)
8
+ @protocol_version = protocol_version
9
+ @raw_rows = raw_rows
10
+ end
11
+
12
+ def materialize(metadata)
13
+ @metadata = metadata
14
+ @rows = RowsResultResponse.read_rows!(@protocol_version, @raw_rows, @metadata)
15
+ end
16
+
17
+ def rows
18
+ raise UnmaterializedRowsError, 'Not materialized!' unless @rows
19
+ @rows
20
+ end
21
+
22
+ def to_s
23
+ %(RESULT ROWS (raw))
24
+ end
25
+ end
26
+ end
27
+ end
@@ -3,7 +3,7 @@
3
3
  module Cql
4
4
  module Protocol
5
5
  class ReadyResponse < Response
6
- def self.decode!(buffer, trace_id=nil)
6
+ def self.decode!(protocol_version, buffer, length, trace_id=nil)
7
7
  new
8
8
  end
9
9
 
@@ -9,11 +9,11 @@ module Cql
9
9
  @trace_id = trace_id
10
10
  end
11
11
 
12
- def self.decode!(buffer, trace_id=nil)
12
+ def self.decode!(protocol_version, buffer, length, trace_id=nil)
13
13
  kind = read_int!(buffer)
14
14
  impl = RESULT_TYPES[kind]
15
15
  raise UnsupportedResultKindError, %(Unsupported result kind: #{kind}) unless impl
16
- impl.decode!(buffer, trace_id)
16
+ impl.decode!(protocol_version, buffer, length - 4, trace_id)
17
17
  end
18
18
 
19
19
  def void?
@@ -3,16 +3,23 @@
3
3
  module Cql
4
4
  module Protocol
5
5
  class RowsResultResponse < ResultResponse
6
- attr_reader :rows, :metadata
6
+ attr_reader :rows, :metadata, :paging_state
7
7
 
8
- def initialize(rows, metadata, trace_id)
8
+ def initialize(rows, metadata, paging_state, trace_id)
9
9
  super(trace_id)
10
- @rows, @metadata = rows, metadata
10
+ @rows, @metadata, @paging_state = rows, metadata, paging_state
11
11
  end
12
12
 
13
- def self.decode!(buffer, trace_id=nil)
14
- column_specs = read_metadata!(buffer)
15
- new(read_rows!(buffer, column_specs), column_specs, trace_id)
13
+ def self.decode!(protocol_version, buffer, length, trace_id=nil)
14
+ original_buffer_length = buffer.length
15
+ column_specs, columns_count, paging_state = read_metadata!(protocol_version, buffer)
16
+ if column_specs.nil?
17
+ consumed_bytes = original_buffer_length - buffer.length
18
+ remaining_bytes = ByteBuffer.new(buffer.read(length - consumed_bytes))
19
+ RawRowsResultResponse.new(protocol_version, remaining_bytes, paging_state, trace_id)
20
+ else
21
+ new(read_rows!(protocol_version, buffer, column_specs), column_specs, paging_state, trace_id)
22
+ end
16
23
  end
17
24
 
18
25
  def to_s
@@ -43,6 +50,12 @@ module Cql
43
50
  :inet,
44
51
  ].freeze
45
52
 
53
+ TYPE_CONVERTER = TypeConverter.new
54
+
55
+ GLOBAL_TABLES_SPEC_FLAG = 0x01
56
+ HAS_MORE_PAGES_FLAG = 0x02
57
+ NO_METADATA_FLAG = 0x04
58
+
46
59
  def self.read_column_type!(buffer)
47
60
  id, type = read_option!(buffer) do |id, b|
48
61
  if id > 0 && id <= 0x10
@@ -64,35 +77,42 @@ module Cql
64
77
  type
65
78
  end
66
79
 
67
- def self.read_metadata!(buffer)
80
+ def self.read_metadata!(protocol_version, buffer)
68
81
  flags = read_int!(buffer)
69
82
  columns_count = read_int!(buffer)
70
- if flags & 0x01 == 0x01
71
- global_keyspace_name = read_string!(buffer)
72
- global_table_name = read_string!(buffer)
83
+ paging_state = nil
84
+ column_specs = nil
85
+ if flags & HAS_MORE_PAGES_FLAG != 0
86
+ paging_state = read_bytes!(buffer)
73
87
  end
74
- column_specs = columns_count.times.map do
75
- if global_keyspace_name
76
- keyspace_name = global_keyspace_name
77
- table_name = global_table_name
78
- else
79
- keyspace_name = read_string!(buffer)
80
- table_name = read_string!(buffer)
88
+ if flags & NO_METADATA_FLAG == 0
89
+ if flags & GLOBAL_TABLES_SPEC_FLAG != 0
90
+ global_keyspace_name = read_string!(buffer)
91
+ global_table_name = read_string!(buffer)
92
+ end
93
+ column_specs = columns_count.times.map do
94
+ if global_keyspace_name
95
+ keyspace_name = global_keyspace_name
96
+ table_name = global_table_name
97
+ else
98
+ keyspace_name = read_string!(buffer)
99
+ table_name = read_string!(buffer)
100
+ end
101
+ column_name = read_string!(buffer)
102
+ type = read_column_type!(buffer)
103
+ [keyspace_name, table_name, column_name, type]
81
104
  end
82
- column_name = read_string!(buffer)
83
- type = read_column_type!(buffer)
84
- [keyspace_name, table_name, column_name, type]
85
105
  end
106
+ [column_specs, columns_count, paging_state]
86
107
  end
87
108
 
88
- def self.read_rows!(buffer, column_specs)
89
- type_converter = TypeConverter.new
109
+ def self.read_rows!(protocol_version, buffer, column_specs)
90
110
  rows_count = read_int!(buffer)
91
111
  rows = []
92
112
  rows_count.times do |row_index|
93
113
  row = {}
94
114
  column_specs.each do |column_spec|
95
- row[column_spec[2]] = type_converter.from_bytes(buffer, column_spec[3])
115
+ row[column_spec[2]] = TYPE_CONVERTER.from_bytes(buffer, column_spec[3])
96
116
  end
97
117
  rows << row
98
118
  end
@@ -12,7 +12,7 @@ module Cql
12
12
  @type = TYPE
13
13
  end
14
14
 
15
- def self.decode!(buffer, trace_id=nil)
15
+ def self.decode!(protocol_version, buffer, length, trace_id=nil)
16
16
  new(read_string!(buffer), read_string!(buffer), read_string!(buffer))
17
17
  end
18
18
 
@@ -10,7 +10,7 @@ module Cql
10
10
  @change, @keyspace, @table = change, keyspace, table
11
11
  end
12
12
 
13
- def self.decode!(buffer, trace_id=nil)
13
+ def self.decode!(protocol_version, buffer, length, trace_id=nil)
14
14
  new(read_string!(buffer), read_string!(buffer), read_string!(buffer), trace_id)
15
15
  end
16
16
 
@@ -10,7 +10,7 @@ module Cql
10
10
  @keyspace = keyspace
11
11
  end
12
12
 
13
- def self.decode!(buffer, trace_id=nil)
13
+ def self.decode!(protocol_version, buffer, length, trace_id=nil)
14
14
  new(read_string!(buffer), trace_id)
15
15
  end
16
16
 
@@ -12,7 +12,7 @@ module Cql
12
12
  @type = TYPE
13
13
  end
14
14
 
15
- def self.decode!(buffer, trace_id=nil)
15
+ def self.decode!(protocol_version, buffer, length, trace_id=nil)
16
16
  new(read_string!(buffer), *read_inet!(buffer))
17
17
  end
18
18
 
@@ -9,7 +9,7 @@ module Cql
9
9
  @options = options
10
10
  end
11
11
 
12
- def self.decode!(buffer, trace_id=nil)
12
+ def self.decode!(protocol_version, buffer, length, trace_id=nil)
13
13
  new(read_string_multimap!(buffer))
14
14
  end
15
15
 
@@ -3,7 +3,7 @@
3
3
  module Cql
4
4
  module Protocol
5
5
  class VoidResultResponse < ResultResponse
6
- def self.decode!(buffer, trace_id=nil)
6
+ def self.decode!(protocol_version, buffer, length, trace_id=nil)
7
7
  new(trace_id)
8
8
  end
9
9
 
@@ -43,7 +43,7 @@ module Cql
43
43
  when :list, :set
44
44
  _, sub_type = type
45
45
  if value
46
- raw = ByteBuffer.new
46
+ raw = ''
47
47
  write_short(raw, value.size)
48
48
  value.each do |element|
49
49
  to_bytes(raw, sub_type, element, 2)
@@ -55,7 +55,7 @@ module Cql
55
55
  when :map
56
56
  _, key_type, value_type = type
57
57
  if value
58
- raw = ByteBuffer.new
58
+ raw = ''
59
59
  write_short(raw, value.size)
60
60
  value.each do |key, value|
61
61
  to_bytes(raw, key_type, key, 2)
@@ -39,7 +39,7 @@ module Cql
39
39
  end
40
40
 
41
41
  def hash
42
- @h ||= 0x7fffffffffffffff - ((@n & 0xffffffffffffffff) ^ ((@n >> 64) & 0xffffffffffffffff))
42
+ @h = (@n & 0xffffffffffffffff) ^ ((@n >> 64) & 0xffffffffffffffff)
43
43
  end
44
44
 
45
45
  # Returns the numerical representation of this UUID
@@ -52,7 +52,7 @@ module Cql
52
52
 
53
53
  # @private
54
54
  def eql?(other)
55
- other.kind_of?(Uuid) && self.value == other.value
55
+ other.respond_to?(:value) && self.value == other.value
56
56
  end
57
57
  alias_method :==, :eql?
58
58
 
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Cql
4
- VERSION = '1.2.2'.freeze
4
+ VERSION = '2.0.0.pre0'.freeze
5
5
  end
@@ -10,10 +10,14 @@ module Cql
10
10
  described_class.new(connection_options)
11
11
  end
12
12
 
13
- let :connection_options do
13
+ let :default_connection_options do
14
14
  {:host => 'example.com', :port => 12321, :io_reactor => io_reactor, :logger => logger}
15
15
  end
16
16
 
17
+ let :connection_options do
18
+ default_connection_options
19
+ end
20
+
17
21
  let :io_reactor do
18
22
  FakeIoReactor.new
19
23
  end
@@ -109,9 +113,11 @@ module Cql
109
113
  Protocol::ReadyResponse.new
110
114
  when Protocol::QueryRequest
111
115
  case request.cql
116
+ when /USE\s+"?(\S+)"?/
117
+ Cql::Protocol::SetKeyspaceResultResponse.new($1, nil)
112
118
  when /FROM system\.local/
113
119
  row = {'host_id' => connection[:spec_host_id], 'data_center' => connection[:spec_data_center]}
114
- Protocol::RowsResultResponse.new([row], local_metadata, nil)
120
+ Protocol::RowsResultResponse.new([row], local_metadata, nil, nil)
115
121
  when /FROM system\.peers/
116
122
  other_host_ids = connections.reject { |c| c[:spec_host_id] == connection[:spec_host_id] }.map { |c| c[:spec_host_id] }
117
123
  until other_host_ids.size >= min_peers[0]
@@ -126,7 +132,7 @@ module Cql
126
132
  'rpc_address' => bind_all_rpc_addresses ? IPAddr.new('0.0.0.0') : ip
127
133
  }
128
134
  end
129
- Protocol::RowsResultResponse.new(rows, peer_metadata, nil)
135
+ Protocol::RowsResultResponse.new(rows, peer_metadata, nil, nil)
130
136
  end
131
137
  end
132
138
  end
@@ -146,6 +152,16 @@ module Cql
146
152
  connections.should have(1).item
147
153
  end
148
154
 
155
+ it 'starts the IO reactor' do
156
+ client.connect.value
157
+ io_reactor.should be_running
158
+ end
159
+
160
+ it 'fails when the IO reactor fails to start' do
161
+ io_reactor.stub(:start).and_return(Future.failed(StandardError.new('bork')))
162
+ expect { client.connect.value }.to raise_error('bork')
163
+ end
164
+
149
165
  context 'when connecting to multiple hosts' do
150
166
  before do
151
167
  client.close.value
@@ -193,6 +209,129 @@ module Cql
193
209
  end
194
210
  end
195
211
 
212
+ context 'when negotiating protocol version' do
213
+ let :client do
214
+ described_class.new(connection_options.merge(protocol_version: 7))
215
+ end
216
+
217
+ it 'tries decreasing protocol versions until one succeeds' do
218
+ counter = 0
219
+ handle_request do |request|
220
+ if counter < 3
221
+ counter += 1
222
+ Protocol::ErrorResponse.new(0x0a, 'Bork version, dummy!')
223
+ elsif counter == 3
224
+ counter += 1
225
+ Protocol::SupportedResponse.new('CQL_VERSION' => %w[3.0.0], 'COMPRESSION' => %w[lz4 snappy])
226
+ else
227
+ Protocol::RowsResultResponse.new([], [], nil, nil)
228
+ end
229
+ end
230
+ client.connect.value
231
+ client.should be_connected
232
+ end
233
+
234
+ it 'logs when it tries the next protocol version' do
235
+ logger.stub(:warn)
236
+ counter = 0
237
+ handle_request do |request|
238
+ if counter < 3
239
+ counter += 1
240
+ Protocol::ErrorResponse.new(0x0a, 'Bork version, dummy!')
241
+ elsif counter == 3
242
+ counter += 1
243
+ Protocol::SupportedResponse.new('CQL_VERSION' => %w[3.0.0], 'COMPRESSION' => %w[lz4 snappy])
244
+ else
245
+ Protocol::RowsResultResponse.new([], [], nil, nil)
246
+ end
247
+ end
248
+ client.connect.value
249
+ logger.should have_received(:warn).with(/could not connect using protocol version 7 \(will try again with 6\): bork version, dummy!/i)
250
+ end
251
+
252
+ it 'gives up when the protocol version is zero' do
253
+ handle_request do |request|
254
+ Protocol::ErrorResponse.new(0x0a, 'Bork version, dummy!')
255
+ end
256
+ expect { client.connect.value }.to raise_error(QueryError)
257
+ client.should_not be_connected
258
+ end
259
+
260
+ it 'gives up when a non-protocol version related error is raised' do
261
+ counter = 0
262
+ handle_request do |request|
263
+ if counter == 4
264
+ Protocol::ErrorResponse.new(0x0a, 'Bork version, dummy!')
265
+ else
266
+ counter += 1
267
+ Protocol::ErrorResponse.new(0x1001, 'Get off my lawn!')
268
+ end
269
+ end
270
+ expect { client.connect.value }.to raise_error(/Get off my lawn/)
271
+ client.should_not be_connected
272
+ end
273
+
274
+ it 'uses different authentication mechanisms for different protocol versions' do
275
+ client = described_class.new(connection_options.merge(protocol_version: 3, credentials: {username: 'foo', password: 'bar'}))
276
+ auth_requests = []
277
+ handle_request do |request|
278
+ case request
279
+ when Protocol::OptionsRequest
280
+ Protocol::SupportedResponse.new('CQL_VERSION' => %w[3.0.0], 'COMPRESSION' => %w[lz4 snappy])
281
+ when Protocol::StartupRequest
282
+ Protocol::AuthenticateResponse.new('org.apache.cassandra.auth.PasswordAuthenticator')
283
+ when Protocol::AuthResponseRequest
284
+ auth_requests << request
285
+ Protocol::ErrorResponse.new(0x0a, 'Bork version, dummy!')
286
+ when Protocol::CredentialsRequest
287
+ auth_requests << request
288
+ Protocol::ErrorResponse.new(0x0a, 'Bork version, dummy!')
289
+ else
290
+ Protocol::ErrorResponse.new(0x0a, 'Bork version, dummy!')
291
+ end
292
+ end
293
+ client.connect.value rescue nil
294
+ auth_requests[0].should be_a(Protocol::AuthResponseRequest)
295
+ auth_requests[1].should be_a(Protocol::AuthResponseRequest)
296
+ auth_requests[2].should be_a(Protocol::CredentialsRequest)
297
+ end
298
+
299
+ it 'fails authenticating when an auth provider has been specified but the protocol is negotiated to v1' do
300
+ client = described_class.new(connection_options.merge(protocol_version: 2, auth_provider: double(:auth_provider)))
301
+ counter = 0
302
+ handle_request do |request|
303
+ if counter == 0
304
+ counter += 1
305
+ Protocol::ErrorResponse.new(0x0a, 'Bork version, dummy!')
306
+ else
307
+ case request
308
+ when Protocol::OptionsRequest
309
+ Protocol::SupportedResponse.new('CQL_VERSION' => %w[3.0.0], 'COMPRESSION' => %w[lz4 snappy])
310
+ when Protocol::StartupRequest
311
+ Protocol::AuthenticateResponse.new('org.apache.cassandra.auth.PasswordAuthenticator')
312
+ end
313
+ end
314
+ end
315
+ expect { client.connect.value }.to raise_error(AuthenticationError)
316
+ counter.should == 1
317
+ end
318
+
319
+ it 'defaults to different CQL versions for the different protocols' do
320
+ cql_versions = []
321
+ handle_request do |request|
322
+ if request.is_a?(Protocol::StartupRequest)
323
+ cql_versions << request.options['CQL_VERSION']
324
+ end
325
+ nil
326
+ end
327
+ [1, 2, 3].map do |protocol_version|
328
+ client = described_class.new(connection_options.merge(protocol_version: protocol_version))
329
+ client.connect.value
330
+ end
331
+ cql_versions.should == ['3.0.0', '3.1.0', '3.1.0']
332
+ end
333
+ end
334
+
196
335
  it 'returns itself' do
197
336
  client.connect.value.should equal(client)
198
337
  end
@@ -213,6 +352,13 @@ module Cql
213
352
  client.keyspace.should be_nil
214
353
  end
215
354
 
355
+ it 'sends the specified CQL version in the startup message' do
356
+ c = described_class.new(connection_options.merge(cql_version: '5.0.1'))
357
+ c.connect.value
358
+ request = requests.find { |rq| rq.is_a?(Protocol::StartupRequest) }
359
+ request.options.should include('CQL_VERSION' => '5.0.1')
360
+ end
361
+
216
362
  it 'enables compression when a compressor is specified' do
217
363
  handle_request do |request|
218
364
  case request
@@ -227,22 +373,54 @@ module Cql
227
373
  request.options.should include('COMPRESSION' => 'lz4')
228
374
  end
229
375
 
376
+ it 'does not enable compression when the algorithm is not supported' do
377
+ handle_request do |request|
378
+ case request
379
+ when Protocol::OptionsRequest
380
+ Protocol::SupportedResponse.new('CQL_VERSION' => %w[3.0.0], 'COMPRESSION' => %w[snappy])
381
+ end
382
+ end
383
+ compressor = double(:compressor, algorithm: 'lz4')
384
+ c = described_class.new(connection_options.merge(compressor: compressor))
385
+ c.connect.value
386
+ request = requests.find { |rq| rq.is_a?(Protocol::StartupRequest) }
387
+ request.options.should_not include('COMPRESSION' => 'lz4')
388
+ end
389
+
390
+ it 'logs a warning when compression was disabled because the algorithm was not supported' do
391
+ logger.stub(:warn)
392
+ handle_request do |request|
393
+ case request
394
+ when Protocol::OptionsRequest
395
+ Protocol::SupportedResponse.new('CQL_VERSION' => %w[3.0.0], 'COMPRESSION' => %w[snappy])
396
+ end
397
+ end
398
+ compressor = double(:compressor, algorithm: 'lz4')
399
+ c = described_class.new(connection_options.merge(compressor: compressor))
400
+ c.connect.value
401
+ logger.should have_received(:warn).with(/not supported/)
402
+ end
403
+
230
404
  it 'changes to the keyspace given as an option' do
231
405
  c = described_class.new(connection_options.merge(:keyspace => 'hello_world'))
232
406
  c.connect.value
233
- request = requests.find { |rq| rq == Protocol::QueryRequest.new('USE hello_world', :one) }
407
+ request = requests.find { |rq| rq == Protocol::QueryRequest.new('USE hello_world', nil, nil, :one) }
234
408
  request.should_not be_nil, 'expected a USE request to have been sent'
235
409
  end
236
410
 
237
411
  it 'validates the keyspace name before sending the USE command' do
238
412
  c = described_class.new(connection_options.merge(:keyspace => 'system; DROP KEYSPACE system'))
239
413
  expect { c.connect.value }.to raise_error(InvalidKeyspaceNameError)
240
- requests.should_not include(Protocol::QueryRequest.new('USE system; DROP KEYSPACE system', :one))
414
+ requests.should_not include(Protocol::QueryRequest.new('USE system; DROP KEYSPACE system', nil, nil, :one))
241
415
  end
242
416
 
243
417
  context 'with automatic peer discovery' do
244
418
  include_context 'peer discovery setup'
245
419
 
420
+ let :connection_options do
421
+ default_connection_options.merge(keyspace: 'foo')
422
+ end
423
+
246
424
  it 'connects to the other nodes in the cluster' do
247
425
  client.connect.value
248
426
  connections.should have(3).items
@@ -290,6 +468,15 @@ module Cql
290
468
  client.connect.value
291
469
  connections.should have(2).items
292
470
  end
471
+
472
+ it 'makes sure the new connections use the specified initial keyspace' do
473
+ client.connect.value
474
+ use_keyspace_requests = connections.map { |c| c.requests.find { |r| r.is_a?(Protocol::QueryRequest) && r.cql.include?('USE') }}
475
+ use_keyspace_requests.should have(3).items
476
+ use_keyspace_requests.each do |rq|
477
+ rq.cql.should match(/USE foo/)
478
+ end
479
+ end
293
480
  end
294
481
 
295
482
  it 're-raises any errors raised' do
@@ -322,21 +509,36 @@ module Cql
322
509
  end
323
510
 
324
511
  context 'when the server requests authentication' do
512
+ let :auth_provider do
513
+ PlainTextAuthProvider.new('foo', 'bar')
514
+ end
515
+
325
516
  def accepting_request_handler(request, *)
326
517
  case request
327
518
  when Protocol::StartupRequest
328
- Protocol::AuthenticateResponse.new('com.example.Auth')
519
+ Protocol::AuthenticateResponse.new('org.apache.cassandra.auth.PasswordAuthenticator')
329
520
  when Protocol::CredentialsRequest
330
521
  Protocol::ReadyResponse.new
522
+ when Protocol::AuthResponseRequest
523
+ Protocol::AuthSuccessResponse.new('hello!')
331
524
  end
332
525
  end
333
526
 
334
527
  def denying_request_handler(request, *)
335
528
  case request
336
529
  when Protocol::StartupRequest
337
- Protocol::AuthenticateResponse.new('com.example.Auth')
530
+ Protocol::AuthenticateResponse.new('org.apache.cassandra.auth.PasswordAuthenticator')
338
531
  when Protocol::CredentialsRequest
339
532
  Protocol::ErrorResponse.new(256, 'No way, José')
533
+ when Protocol::AuthResponseRequest
534
+ Protocol::ErrorResponse.new(256, 'No way, José')
535
+ end
536
+ end
537
+
538
+ def custom_request_handler(request, *)
539
+ case request
540
+ when Protocol::StartupRequest
541
+ Protocol::AuthenticateResponse.new('org.acme.Auth')
340
542
  end
341
543
  end
342
544
 
@@ -344,11 +546,34 @@ module Cql
344
546
  handle_request(&method(:accepting_request_handler))
345
547
  end
346
548
 
347
- it 'sends credentials' do
348
- client = described_class.new(connection_options.merge(credentials: {'username' => 'foo', 'password' => 'bar'}))
349
- client.connect.value
350
- request = requests.find { |rq| rq == Protocol::CredentialsRequest.new('username' => 'foo', 'password' => 'bar') }
351
- request.should_not be_nil, 'expected a credentials request to have been sent'
549
+ context 'with protocol v1' do
550
+ it 'uses an auth provider to authenticate' do
551
+ client = described_class.new(connection_options.merge(credentials: {:username => 'foo', :password => 'bar'}, protocol_version: 1))
552
+ client.connect.value
553
+ request = requests.find { |rq| rq.is_a?(Protocol::CredentialsRequest) }
554
+ request.credentials.should eql(:username => 'foo', :password => 'bar')
555
+ end
556
+
557
+ it 'fails to authenticate when only an auth provider has been specified' do
558
+ client = described_class.new(connection_options.merge(auth_provider: auth_provider, protocol_version: 1))
559
+ expect { client.connect.value }.to raise_error(AuthenticationError)
560
+ end
561
+ end
562
+
563
+ context 'with protocol v2' do
564
+ it 'uses an auth provider to authenticate' do
565
+ client = described_class.new(connection_options.merge(auth_provider: auth_provider))
566
+ client.connect.value
567
+ request = requests.find { |rq| rq == Protocol::AuthResponseRequest.new("\x00foo\x00bar") }
568
+ request.should_not be_nil, 'expected an auth response request to have been sent'
569
+ end
570
+
571
+ it 'creates a PlainTextAuthProvider when the :credentials options is given' do
572
+ client = described_class.new(connection_options.merge(credentials: {:username => 'foo', :password => 'bar'}))
573
+ client.connect.value
574
+ request = requests.find { |rq| rq == Protocol::AuthResponseRequest.new("\x00foo\x00bar") }
575
+ request.should_not be_nil, 'expected a auth response request to have been sent'
576
+ end
352
577
  end
353
578
 
354
579
  it 'raises an error when no credentials have been given' do
@@ -358,13 +583,19 @@ module Cql
358
583
 
359
584
  it 'raises an error when the server responds with an error to the credentials request' do
360
585
  handle_request(&method(:denying_request_handler))
361
- client = described_class.new(connection_options.merge(credentials: {'username' => 'foo', 'password' => 'bar'}))
586
+ client = described_class.new(connection_options.merge(connection_options.merge(auth_provider: auth_provider)))
587
+ expect { client.connect.value }.to raise_error(AuthenticationError)
588
+ end
589
+
590
+ it 'raises an error when the server requests authentication that the auth provider does not support' do
591
+ handle_request(&method(:custom_request_handler))
592
+ client = described_class.new(connection_options.merge(connection_options.merge(auth_provider: auth_provider)))
362
593
  expect { client.connect.value }.to raise_error(AuthenticationError)
363
594
  end
364
595
 
365
596
  it 'shuts down the client when there is an authentication error' do
366
597
  handle_request(&method(:denying_request_handler))
367
- client = described_class.new(connection_options.merge(credentials: {'username' => 'foo', 'password' => 'bar'}))
598
+ client = described_class.new(connection_options.merge(connection_options.merge(auth_provider: auth_provider)))
368
599
  client.connect.value rescue nil
369
600
  client.should_not be_connected
370
601
  io_reactor.should_not be_running
@@ -403,6 +634,38 @@ module Cql
403
634
  client.close.value
404
635
  expect { client.connect.value }.to raise_error(ClientError)
405
636
  end
637
+
638
+ it 'waits for #connect to complete before attempting to close' do
639
+ order = []
640
+ reactor_start_promise = Promise.new
641
+ io_reactor.stub(:start).and_return(reactor_start_promise.future)
642
+ io_reactor.stub(:stop).and_return(Future.resolved)
643
+ connected = client.connect
644
+ connected.on_value { order << :connected }
645
+ closed = client.close
646
+ closed.on_value { order << :closed }
647
+ connected.should_not be_completed
648
+ reactor_start_promise.fulfill
649
+ connected.value
650
+ closed.value
651
+ order.should == [:connected, :closed]
652
+ end
653
+
654
+ it 'waits for #connect to complete before attempting to close, when #connect fails' do
655
+ order = []
656
+ reactor_start_promise = Promise.new
657
+ io_reactor.stub(:start).and_return(reactor_start_promise.future)
658
+ io_reactor.stub(:stop).and_return(Future.resolved)
659
+ connected = client.connect
660
+ connected.on_failure { order << :connect_failed }
661
+ closed = client.close
662
+ closed.on_value { order << :closed }
663
+ connected.should_not be_completed
664
+ reactor_start_promise.fail(StandardError.new('bork'))
665
+ connected.value rescue nil
666
+ closed.value
667
+ order.should == [:connect_failed, :closed]
668
+ end
406
669
  end
407
670
 
408
671
  describe '#use' do
@@ -414,7 +677,7 @@ module Cql
414
677
  end
415
678
  client.connect.value
416
679
  client.use('system').value
417
- last_request.should == Protocol::QueryRequest.new('USE system', :one)
680
+ last_request.should == Protocol::QueryRequest.new('USE system', nil, nil, :one)
418
681
  end
419
682
 
420
683
  it 'executes a USE query for each connection' do
@@ -428,9 +691,9 @@ module Cql
428
691
  c.use('system').value
429
692
  last_requests = connections.select { |c| c.host =~ /^h\d\.example\.com$/ }.sort_by(&:host).map { |c| c.requests.last }
430
693
  last_requests.should == [
431
- Protocol::QueryRequest.new('USE system', :one),
432
- Protocol::QueryRequest.new('USE system', :one),
433
- Protocol::QueryRequest.new('USE system', :one)
694
+ Protocol::QueryRequest.new('USE system', nil, nil, :one),
695
+ Protocol::QueryRequest.new('USE system', nil, nil, :one),
696
+ Protocol::QueryRequest.new('USE system', nil, nil, :one),
434
697
  ]
435
698
  end
436
699
 
@@ -449,17 +712,6 @@ module Cql
449
712
  client.connect.value
450
713
  expect { client.use('system; DROP KEYSPACE system').value }.to raise_error(InvalidKeyspaceNameError)
451
714
  end
452
-
453
- it 'allows the keyspace name to be quoted' do
454
- handle_request do |request|
455
- if request.is_a?(Protocol::QueryRequest) && request.cql == 'USE "system"'
456
- Protocol::SetKeyspaceResultResponse.new('system', nil)
457
- end
458
- end
459
- client.connect.value
460
- client.use('"system"').value
461
- client.keyspace.should == "system"
462
- end
463
715
  end
464
716
 
465
717
  describe '#execute' do
@@ -473,24 +725,46 @@ module Cql
473
725
 
474
726
  it 'asks the connection to execute the query using the default consistency level' do
475
727
  client.execute(cql).value
476
- last_request.should == Protocol::QueryRequest.new(cql, :quorum)
728
+ last_request.should == Protocol::QueryRequest.new(cql, nil, nil, :quorum)
477
729
  end
478
730
 
479
731
  it 'uses the consistency specified when the client was created' do
480
732
  client = described_class.new(connection_options.merge(default_consistency: :all))
481
733
  client.connect.value
482
734
  client.execute(cql).value
483
- last_request.should == Protocol::QueryRequest.new(cql, :all)
735
+ last_request.should == Protocol::QueryRequest.new(cql, nil, nil, :all)
484
736
  end
485
737
 
486
738
  it 'uses the consistency given as last argument' do
487
739
  client.execute('UPDATE stuff SET thing = 1 WHERE id = 3', :three).value
488
- last_request.should == Protocol::QueryRequest.new('UPDATE stuff SET thing = 1 WHERE id = 3', :three)
740
+ last_request.should == Protocol::QueryRequest.new('UPDATE stuff SET thing = 1 WHERE id = 3', nil, nil, :three)
489
741
  end
490
742
 
491
743
  it 'uses the consistency given as an option' do
492
744
  client.execute('UPDATE stuff SET thing = 1 WHERE id = 3', consistency: :local_quorum).value
493
- last_request.should == Protocol::QueryRequest.new('UPDATE stuff SET thing = 1 WHERE id = 3', :local_quorum)
745
+ last_request.should == Protocol::QueryRequest.new('UPDATE stuff SET thing = 1 WHERE id = 3', nil, nil, :local_quorum)
746
+ end
747
+
748
+ context 'with multiple arguments' do
749
+ it 'passes the arguments as bound values' do
750
+ client.execute('UPDATE stuff SET thing = ? WHERE id = ?', 'foo', 'bar').value
751
+ last_request.should == Protocol::QueryRequest.new('UPDATE stuff SET thing = ? WHERE id = ?', ['foo', 'bar'], nil, :quorum)
752
+ end
753
+
754
+ it 'passes the type hints option to the request' do
755
+ client.execute('UPDATE stuff SET thing = ? WHERE id = ?', 'foo', 3, type_hints: [nil, :int]).value
756
+ last_request.should == Protocol::QueryRequest.new('UPDATE stuff SET thing = ? WHERE id = ?', ['foo', 3], [nil, :int], :quorum)
757
+ end
758
+
759
+ it 'detects when the last argument is the consistency' do
760
+ client.execute('UPDATE stuff SET thing = ? WHERE id = ?', 'foo', 'bar', :each_quorum).value
761
+ last_request.should == Protocol::QueryRequest.new('UPDATE stuff SET thing = ? WHERE id = ?', ['foo', 'bar'], nil, :each_quorum)
762
+ end
763
+
764
+ it 'detects when the last arguments is an options hash' do
765
+ client.execute('UPDATE stuff SET thing = ? WHERE id = ?', 'foo', 'bar', consistency: :all, tracing: true).value
766
+ last_request.should == Protocol::QueryRequest.new('UPDATE stuff SET thing = ? WHERE id = ?', ['foo', 'bar'], nil, :all, nil, nil, nil, true)
767
+ end
494
768
  end
495
769
 
496
770
  context 'with a void CQL query' do
@@ -545,9 +819,9 @@ module Cql
545
819
 
546
820
  last_requests = connections.select { |c| c.host =~ /^h\d\.example\.com$/ }.sort_by(&:host).map { |c| c.requests.last }
547
821
  last_requests.should == [
548
- Protocol::QueryRequest.new('USE system', :one),
549
- Protocol::QueryRequest.new('USE system', :one),
550
- Protocol::QueryRequest.new('USE system', :one)
822
+ Protocol::QueryRequest.new('USE system', nil, nil, :one),
823
+ Protocol::QueryRequest.new('USE system', nil, nil, :one),
824
+ Protocol::QueryRequest.new('USE system', nil, nil, :one),
551
825
  ]
552
826
  end
553
827
  end
@@ -568,7 +842,7 @@ module Cql
568
842
  before do
569
843
  handle_request do |request|
570
844
  if request.is_a?(Protocol::QueryRequest) && request.cql =~ /FROM things/
571
- Protocol::RowsResultResponse.new(rows, metadata, nil)
845
+ Protocol::RowsResultResponse.new(rows, metadata, nil, nil)
572
846
  end
573
847
  end
574
848
  end
@@ -662,13 +936,76 @@ module Cql
662
936
  trace_id = Uuid.new('a1028490-3f05-11e3-9531-fb72eff05fbb')
663
937
  handle_request do |request|
664
938
  if request.is_a?(Protocol::QueryRequest) && request.cql == cql
665
- Protocol::RowsResultResponse.new([], [], trace_id)
939
+ Protocol::RowsResultResponse.new([], [], nil, trace_id)
666
940
  end
667
941
  end
668
942
  result = client.execute(cql, trace: true).value
669
943
  result.trace_id.should == trace_id
670
944
  end
671
945
  end
946
+
947
+ context 'with paging' do
948
+ let :cql do
949
+ 'SELECT * FROM foo'
950
+ end
951
+
952
+ let :rows do
953
+ [['xyz', 'abc'], ['abc', 'xyz'], ['123', 'xyz']]
954
+ end
955
+
956
+ let :metadata do
957
+ [['thingies', 'things', 'thing', :text], ['thingies', 'things', 'item', :text]]
958
+ end
959
+
960
+ before do
961
+ handle_request do |request|
962
+ if request.is_a?(Protocol::QueryRequest) && request.cql == cql
963
+ if request.paging_state.nil?
964
+ Protocol::RowsResultResponse.new(rows.take(2), metadata, 'foobar', nil)
965
+ elsif request.paging_state == 'foobar'
966
+ Protocol::RowsResultResponse.new(rows.drop(2), metadata, nil, nil)
967
+ end
968
+ end
969
+ end
970
+ end
971
+
972
+ it 'sets the page size' do
973
+ client.execute(cql, page_size: 100).value
974
+ last_request.page_size.should == 100
975
+ end
976
+
977
+ it 'sets the page size and paging state' do
978
+ client.execute(cql, page_size: 100, paging_state: 'foobar').value
979
+ last_request.page_size.should == 100
980
+ last_request.paging_state.should == 'foobar'
981
+ end
982
+
983
+ it 'returns a result which can load the next page' do
984
+ result = client.execute(cql, page_size: 2).value
985
+ result.next_page.value
986
+ last_request.paging_state.should == 'foobar'
987
+ end
988
+
989
+ it 'returns a result which knows when there are no more pages' do
990
+ result = client.execute(cql, page_size: 2).value
991
+ result = result.next_page.value
992
+ result.should be_last_page
993
+ end
994
+
995
+ it 'passes the same options when loading the next page' do
996
+ sent_timeout = nil
997
+ handle_request do |_, _, _, timeout|
998
+ sent_timeout = timeout
999
+ nil
1000
+ end
1001
+ result = client.execute('SELECT * FROM something WHERE x = ? AND y = ?', 'foo', 'bar', page_size: 100, paging_state: 'foobar', trace: true, timeout: 3, type_hints: [:varchar, nil]).value
1002
+ result.next_page.value
1003
+ last_request.trace.should be_true
1004
+ last_request.values.should == ['foo', 'bar']
1005
+ last_request.type_hints.should == [:varchar, nil]
1006
+ sent_timeout.should == 3
1007
+ end
1008
+ end
672
1009
  end
673
1010
 
674
1011
  describe '#prepare' do
@@ -687,7 +1024,7 @@ module Cql
687
1024
  before do
688
1025
  handle_request do |request|
689
1026
  if request.is_a?(Protocol::PrepareRequest)
690
- Protocol::PreparedResultResponse.new(id, metadata, nil)
1027
+ Protocol::PreparedResultResponse.new(id, metadata, nil, nil)
691
1028
  end
692
1029
  end
693
1030
  end
@@ -709,7 +1046,7 @@ module Cql
709
1046
  it 'executes a prepared statement using the default consistency level' do
710
1047
  statement = client.prepare(cql).value
711
1048
  statement.execute('foo').value
712
- last_request.should == Protocol::ExecuteRequest.new(id, metadata, ['foo'], :quorum)
1049
+ last_request.should == Protocol::ExecuteRequest.new(id, metadata, ['foo'], true, :quorum)
713
1050
  end
714
1051
 
715
1052
  it 'executes a prepared statement using the consistency specified when the client was created' do
@@ -717,7 +1054,7 @@ module Cql
717
1054
  client.connect.value
718
1055
  statement = client.prepare(cql).value
719
1056
  statement.execute('foo').value
720
- last_request.should == Protocol::ExecuteRequest.new(id, metadata, ['foo'], :all)
1057
+ last_request.should == Protocol::ExecuteRequest.new(id, metadata, ['foo'], true, :all)
721
1058
  end
722
1059
 
723
1060
  it 'returns a prepared statement that knows the metadata' do
@@ -728,7 +1065,7 @@ module Cql
728
1065
  it 'executes a prepared statement with a specific consistency level' do
729
1066
  statement = client.prepare(cql).value
730
1067
  statement.execute('thing', :local_quorum).value
731
- last_request.should == Protocol::ExecuteRequest.new(id, metadata, ['thing'], :local_quorum)
1068
+ last_request.should == Protocol::ExecuteRequest.new(id, metadata, ['thing'], true, :local_quorum)
732
1069
  end
733
1070
 
734
1071
  context 'when there is an error creating the request' do
@@ -742,7 +1079,7 @@ module Cql
742
1079
  it 'returns a failed future' do
743
1080
  handle_request do |request|
744
1081
  if request.is_a?(Protocol::PrepareRequest)
745
- Protocol::PreparedResultResponse.new(id, metadata, nil)
1082
+ Protocol::PreparedResultResponse.new(id, metadata, nil, nil)
746
1083
  end
747
1084
  end
748
1085
  statement = client.prepare(cql).value
@@ -753,7 +1090,7 @@ module Cql
753
1090
 
754
1091
  context 'with multiple connections' do
755
1092
  let :connection_options do
756
- {:hosts => %w[host1 host2], :port => 12321, :io_reactor => io_reactor, :logger => logger}
1093
+ default_connection_options.merge(hosts: %w[host1 host2])
757
1094
  end
758
1095
 
759
1096
  it 'prepares the statement on all connections' do
@@ -764,11 +1101,102 @@ module Cql
764
1101
  raise 'Did not receive EXECUTE requests on all connections within 5s' if (Time.now - started_at) > 5
765
1102
  end
766
1103
  connections.map { |c| c.requests.last }.should == [
767
- Protocol::ExecuteRequest.new(id, metadata, ['hello'], :quorum),
768
- Protocol::ExecuteRequest.new(id, metadata, ['hello'], :quorum),
1104
+ Protocol::ExecuteRequest.new(id, metadata, ['hello'], true, :quorum),
1105
+ Protocol::ExecuteRequest.new(id, metadata, ['hello'], true, :quorum),
769
1106
  ]
770
1107
  end
771
1108
  end
1109
+
1110
+ context 'with paging' do
1111
+ let :statement do
1112
+ client.prepare(cql).value
1113
+ end
1114
+
1115
+ let :rows do
1116
+ [['xyz', 'abc'], ['abc', 'xyz'], ['123', 'xyz']]
1117
+ end
1118
+
1119
+ let :metadata do
1120
+ [['thingies', 'things', 'thing', :text]]
1121
+ end
1122
+
1123
+ let :result_metadata do
1124
+ [['thingies', 'things', 'thing', :text], ['thingies', 'things', 'item', :text]]
1125
+ end
1126
+
1127
+ before do
1128
+ handle_request do |request|
1129
+ case request
1130
+ when Protocol::PrepareRequest
1131
+ Protocol::PreparedResultResponse.new(id, metadata, nil, nil)
1132
+ when Protocol::ExecuteRequest
1133
+ if request.paging_state.nil?
1134
+ Protocol::RowsResultResponse.new(rows.take(2), result_metadata, 'foobar', nil)
1135
+ elsif request.paging_state == 'foobar'
1136
+ Protocol::RowsResultResponse.new(rows.drop(2), result_metadata, nil, nil)
1137
+ end
1138
+ end
1139
+ end
1140
+ end
1141
+
1142
+ it 'sets the page size' do
1143
+ statement.execute('foo', page_size: 100).value
1144
+ last_request.page_size.should == 100
1145
+ end
1146
+
1147
+ it 'sets the page size and paging state' do
1148
+ statement.execute('foo', page_size: 100, paging_state: 'foobar').value
1149
+ last_request.page_size.should == 100
1150
+ last_request.paging_state.should == 'foobar'
1151
+ end
1152
+ end
1153
+ end
1154
+
1155
+ describe '#batch' do
1156
+ before do
1157
+ client.connect.value
1158
+ end
1159
+
1160
+ context 'when called witout a block' do
1161
+ it 'returns a batch' do
1162
+ batch = client.batch
1163
+ batch.add('UPDATE x SET y = 3 WHERE z = 1')
1164
+ batch.execute.value
1165
+ last_request.should be_a(Protocol::BatchRequest)
1166
+ end
1167
+
1168
+ it 'creates a batch of the right type' do
1169
+ batch = client.batch(:unlogged)
1170
+ batch.add('UPDATE x SET y = 3 WHERE z = 1')
1171
+ batch.execute.value
1172
+ last_request.type.should == Protocol::BatchRequest::UNLOGGED_TYPE
1173
+ end
1174
+
1175
+ it 'passes the options to the batch' do
1176
+ batch = client.batch(trace: true)
1177
+ batch.add('UPDATE x SET y = 3 WHERE z = 1')
1178
+ batch.execute.value
1179
+ last_request.trace.should be_true
1180
+ end
1181
+ end
1182
+
1183
+ context 'when called with a block' do
1184
+ it 'yields and executes a batch' do
1185
+ f = client.batch do |batch|
1186
+ batch.add('UPDATE x SET y = 3 WHERE z = 1')
1187
+ end
1188
+ f.value
1189
+ last_request.should be_a(Protocol::BatchRequest)
1190
+ end
1191
+
1192
+ it 'passes the options to the batch\'s #execute' do
1193
+ f = client.batch(:unlogged, trace: true) do |batch|
1194
+ batch.add('UPDATE x SET y = 3 WHERE z = 1')
1195
+ end
1196
+ f.value
1197
+ last_request.trace.should be_true
1198
+ end
1199
+ end
772
1200
  end
773
1201
 
774
1202
  context 'when not connected' do
@@ -815,7 +1243,7 @@ module Cql
815
1243
  it 'complains when #execute of a prepared statement is called after #close' do
816
1244
  handle_request do |request|
817
1245
  if request.is_a?(Protocol::PrepareRequest)
818
- Protocol::PreparedResultResponse.new('A' * 32, [], nil)
1246
+ Protocol::PreparedResultResponse.new('A' * 32, [], nil, nil)
819
1247
  end
820
1248
  end
821
1249
  client.connect.value
@@ -829,7 +1257,7 @@ module Cql
829
1257
  include_context 'peer discovery setup'
830
1258
 
831
1259
  let :connection_options do
832
- {:hosts => %w[host1 host2 host3], :port => 12321, :io_reactor => io_reactor}
1260
+ default_connection_options.merge(hosts: %w[host1 host2 host3], keyspace: 'foo')
833
1261
  end
834
1262
 
835
1263
  before do
@@ -860,6 +1288,14 @@ module Cql
860
1288
  connections.select(&:connected?).should have(3).items
861
1289
  end
862
1290
 
1291
+ it 'makes sure the new connections use the same keyspace as the existing' do
1292
+ connections.first.close
1293
+ event = Protocol::TopologyChangeEventResponse.new('NEW_NODE', IPAddr.new('1.1.1.1'), 9999)
1294
+ connections.select(&:has_event_listener?).first.trigger_event(event)
1295
+ use_keyspace_request = connections.last.requests.find { |r| r.is_a?(Protocol::QueryRequest) && r.cql.include?('USE') }
1296
+ use_keyspace_request.cql.should == 'USE foo'
1297
+ end
1298
+
863
1299
  it 'eventually reconnects even when the node doesn\'t respond at first' do
864
1300
  timer_promise = Promise.new
865
1301
  io_reactor.stub(:schedule_timer).and_return(timer_promise.future)
@@ -947,14 +1383,21 @@ module Cql
947
1383
  logger.stub(:warn)
948
1384
  io_reactor.stub(:connect).and_return(Future.failed(StandardError.new('Hurgh blurgh')))
949
1385
  client.connect.value rescue nil
950
- logger.should have_received(:warn).with(/Failed connecting to node at example\.com:12321: Hurgh blurgh/)
1386
+ logger.should have_received(:warn).with(/Failed connecting to node at example\.com: Hurgh blurgh/)
951
1387
  end
952
1388
 
953
1389
  it 'logs when a connection fails' do
954
1390
  logger.stub(:warn)
955
1391
  client.connect.value
1392
+ connections.sample.close(StandardError.new('bork'))
1393
+ logger.should have_received(:warn).with(/Connection to node .{36} at .+:\d+ in data center .+ closed unexpectedly: bork/)
1394
+ end
1395
+
1396
+ it 'logs when a connection closes' do
1397
+ logger.stub(:info)
1398
+ client.connect.value
956
1399
  connections.sample.close
957
- logger.should have_received(:warn).with(/Connection to node .{36} at .+:\d+ in data center .+ unexpectedly closed/)
1400
+ logger.should have_received(:info).with(/Connection to node .{36} at .+:\d+ in data center .+ closed/)
958
1401
  end
959
1402
 
960
1403
  it 'logs when it does a peer discovery' do