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
@@ -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