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
@@ -9,7 +9,8 @@ module Cql
9
9
  describe '.decode!' do
10
10
  context 'with rows from the same table' do
11
11
  let :response do
12
- described_class.decode!(ByteBuffer.new("\x00\x00\x00\x01\x00\x00\x00\x03\x00\ncql_rb_126\x00\x05users\x00\tuser_name\x00\r\x00\x05email\x00\r\x00\bpassword\x00\r\x00\x00\x00\x02\x00\x00\x00\x04phil\x00\x00\x00\rphil@heck.com\xFF\xFF\xFF\xFF\x00\x00\x00\x03sue\x00\x00\x00\rsue@inter.net\xFF\xFF\xFF\xFF"))
12
+ buffer = ByteBuffer.new("\x00\x00\x00\x01\x00\x00\x00\x03\x00\ncql_rb_126\x00\x05users\x00\tuser_name\x00\r\x00\x05email\x00\r\x00\bpassword\x00\r\x00\x00\x00\x02\x00\x00\x00\x04phil\x00\x00\x00\rphil@heck.com\xFF\xFF\xFF\xFF\x00\x00\x00\x03sue\x00\x00\x00\rsue@inter.net\xFF\xFF\xFF\xFF")
13
+ described_class.decode!(1, buffer, buffer.length)
13
14
  end
14
15
 
15
16
  it 'decodes the rows as hashes of column name => column value' do
@@ -43,7 +44,7 @@ module Cql
43
44
  buffer << "\x00\ncql_rb_127\x00\x06users2\x00\x05email\x00\r"
44
45
  buffer << "\x00\ncql_rb_128\x00\x06users3\x00\bpassword\x00\r"
45
46
  buffer << "\x00\x00\x00\x02\x00\x00\x00\x04phil\x00\x00\x00\rphil@heck.com\xFF\xFF\xFF\xFF\x00\x00\x00\x03sue\x00\x00\x00\rsue@inter.net\xFF\xFF\xFF\xFF"
46
- described_class.decode!(buffer)
47
+ described_class.decode!(1, buffer, buffer.length)
47
48
  end
48
49
 
49
50
  it 'decodes the rows' do
@@ -62,6 +63,46 @@ module Cql
62
63
  end
63
64
  end
64
65
 
66
+ context 'when there is no metadata' do
67
+ let :response do
68
+ buffer = ByteBuffer.new
69
+ buffer << "\x00\x00\x00\x05" # flags (global_tables_spec | no_metadata)
70
+ buffer << "\x00\x00\x00\x03" # column count
71
+ buffer << "\x00\x00\x00\x02\x00\x00\x00\x04phil\x00\x00\x00\rphil@heck.com\xFF\xFF\xFF\xFF\x00\x00\x00\x03sue\x00\x00\x00\rsue@inter.net\xFF\xFF\xFF\xFF"
72
+ described_class.decode!(2, buffer, buffer.length)
73
+ end
74
+
75
+ it 'returns a RawRowsResultResponse' do
76
+ metadata = [
77
+ ['cql_rb_126', 'users', 'user_name', :varchar],
78
+ ['cql_rb_126', 'users', 'email', :varchar],
79
+ ['cql_rb_126', 'users', 'password', :varchar]
80
+ ]
81
+ response.materialize(metadata)
82
+ response.rows.should == [
83
+ {'user_name' => 'phil', 'email' => 'phil@heck.com', 'password' => nil},
84
+ {'user_name' => 'sue', 'email' => 'sue@inter.net', 'password' => nil}
85
+ ]
86
+ end
87
+ end
88
+
89
+ context 'when there are more pages' do
90
+ let :response do
91
+ buffer = ByteBuffer.new
92
+ buffer << "\x00\x00\x00\x03" # flags (global_tables_spec | has_more_pages)
93
+ buffer << "\x00\x00\x00\x03" # column count
94
+ buffer << "\x00\x00\x00\x03foo" # paging state
95
+ buffer << "\x00\ncql_rb_126\x00\x05users"
96
+ buffer << "\x00\tuser_name\x00\r\x00\x05email\x00\r\x00\bpassword\x00\r"
97
+ buffer << "\x00\x00\x00\x02\x00\x00\x00\x04phil\x00\x00\x00\rphil@heck.com\xFF\xFF\xFF\xFF\x00\x00\x00\x03sue\x00\x00\x00\rsue@inter.net\xFF\xFF\xFF\xFF"
98
+ described_class.decode!(2, buffer, buffer.length)
99
+ end
100
+
101
+ it 'extracts the paging state' do
102
+ response.paging_state.should == 'foo'
103
+ end
104
+ end
105
+
65
106
  context 'with different column types' do
66
107
  let :response do
67
108
  # The following test was created by intercepting the frame for the
@@ -113,8 +154,8 @@ module Cql
113
154
  # );
114
155
  #
115
156
  # SELECT * FROM lots_of_types WHERE ascii_column = 'hello';
116
-
117
- described_class.decode!(ByteBuffer.new("\x00\x00\x00\x01\x00\x00\x00\x12\x00\x04test\x00\rlots_of_types\x00\fascii_column\x00\x01\x00\rbigint_column\x00\x02\x00\vblob_column\x00\x03\x00\x0Eboolean_column\x00\x04\x00\x0Edecimal_column\x00\x06\x00\rdouble_column\x00\a\x00\ffloat_column\x00\b\x00\vinet_column\x00\x10\x00\nint_column\x00\t\x00\vlist_column\x00 \x00\x01\x00\nmap_column\x00!\x00\r\x00\x04\x00\nset_column\x00\"\x00\x03\x00\vtext_column\x00\r\x00\x10timestamp_column\x00\v\x00\x0Ftimeuuid_column\x00\x0F\x00\vuuid_column\x00\f\x00\x0Evarchar_column\x00\r\x00\rvarint_column\x00\x0E\x00\x00\x00\x01\x00\x00\x00\x05hello\x00\x00\x00\b\x00\x03\x98\xB1S\xC8\x7F\xAB\x00\x00\x00\x05\xFA\xB4^4V\x00\x00\x00\x01\x01\x00\x00\x00\x11\x00\x00\x00\x12\r'\xFDI\xAD\x80f\x11g\xDCfV\xAA\x00\x00\x00\b@\xC3\x88\x0F\xC2\x7F\x9DU\x00\x00\x00\x04AB\x14{\x00\x00\x00\x04\n\x00\x01\x02\x00\x00\x00\x04\x00\xBCj\xC2\x00\x00\x00\x11\x00\x03\x00\x03foo\x00\x03foo\x00\x03bar\x00\x00\x00\x14\x00\x02\x00\x03foo\x00\x01\x01\x00\x05hello\x00\x01\x00\x00\x00\x00\r\x00\x02\x00\x03\xABC!\x00\x04\xAF\xD8~\xCD\x00\x00\x00\vhello world\x00\x00\x00\b\x00\x00\x01</\xE9\xDC\xE3\x00\x00\x00\x10\xA4\xA7\t\x00$\xE1\x11\xDF\x89$\x00\x1F\xF3Y\x17\x11\x00\x00\x00\x10\xCF\xD6l\xCC\xD8WN\x90\xB1\xE5\xDF\x98\xA3\xD4\f\xD6\x00\x00\x00\x03foo\x00\x00\x00\x11\x03\x9EV \x15\f\x03\x9DK\x18\xCDI\\$?\a["))
157
+ buffer = ByteBuffer.new("\x00\x00\x00\x01\x00\x00\x00\x12\x00\x04test\x00\rlots_of_types\x00\fascii_column\x00\x01\x00\rbigint_column\x00\x02\x00\vblob_column\x00\x03\x00\x0Eboolean_column\x00\x04\x00\x0Edecimal_column\x00\x06\x00\rdouble_column\x00\a\x00\ffloat_column\x00\b\x00\vinet_column\x00\x10\x00\nint_column\x00\t\x00\vlist_column\x00 \x00\x01\x00\nmap_column\x00!\x00\r\x00\x04\x00\nset_column\x00\"\x00\x03\x00\vtext_column\x00\r\x00\x10timestamp_column\x00\v\x00\x0Ftimeuuid_column\x00\x0F\x00\vuuid_column\x00\f\x00\x0Evarchar_column\x00\r\x00\rvarint_column\x00\x0E\x00\x00\x00\x01\x00\x00\x00\x05hello\x00\x00\x00\b\x00\x03\x98\xB1S\xC8\x7F\xAB\x00\x00\x00\x05\xFA\xB4^4V\x00\x00\x00\x01\x01\x00\x00\x00\x11\x00\x00\x00\x12\r'\xFDI\xAD\x80f\x11g\xDCfV\xAA\x00\x00\x00\b@\xC3\x88\x0F\xC2\x7F\x9DU\x00\x00\x00\x04AB\x14{\x00\x00\x00\x04\n\x00\x01\x02\x00\x00\x00\x04\x00\xBCj\xC2\x00\x00\x00\x11\x00\x03\x00\x03foo\x00\x03foo\x00\x03bar\x00\x00\x00\x14\x00\x02\x00\x03foo\x00\x01\x01\x00\x05hello\x00\x01\x00\x00\x00\x00\r\x00\x02\x00\x03\xABC!\x00\x04\xAF\xD8~\xCD\x00\x00\x00\vhello world\x00\x00\x00\b\x00\x00\x01</\xE9\xDC\xE3\x00\x00\x00\x10\xA4\xA7\t\x00$\xE1\x11\xDF\x89$\x00\x1F\xF3Y\x17\x11\x00\x00\x00\x10\xCF\xD6l\xCC\xD8WN\x90\xB1\xE5\xDF\x98\xA3\xD4\f\xD6\x00\x00\x00\x03foo\x00\x00\x00\x11\x03\x9EV \x15\f\x03\x9DK\x18\xCDI\\$?\a[")
158
+ described_class.decode!(1, buffer, buffer.length)
118
159
  end
119
160
 
120
161
  it 'decodes ASCII as an ASCII encoded string' do
@@ -196,7 +237,8 @@ module Cql
196
237
 
197
238
  context 'with null values' do
198
239
  it 'decodes nulls' do
199
- response = described_class.decode!(ByteBuffer.new("\x00\x00\x00\x01\x00\x00\x00\x13\x00\x12cql_rb_client_spec\x00\rlots_of_types\x00\x02id\x00\t\x00\fascii_column\x00\x01\x00\rbigint_column\x00\x02\x00\vblob_column\x00\x03\x00\x0Eboolean_column\x00\x04\x00\x0Edecimal_column\x00\x06\x00\rdouble_column\x00\a\x00\ffloat_column\x00\b\x00\vinet_column\x00\x10\x00\nint_column\x00\t\x00\vlist_column\x00 \x00\x01\x00\nmap_column\x00!\x00\r\x00\x04\x00\nset_column\x00\"\x00\x03\x00\vtext_column\x00\r\x00\x10timestamp_column\x00\v\x00\x0Ftimeuuid_column\x00\x0F\x00\vuuid_column\x00\f\x00\x0Evarchar_column\x00\r\x00\rvarint_column\x00\x0E\x00\x00\x00\x01\x00\x00\x00\x04\x00\x00\x00\x03\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"))
240
+ buffer = ByteBuffer.new("\x00\x00\x00\x01\x00\x00\x00\x13\x00\x12cql_rb_client_spec\x00\rlots_of_types\x00\x02id\x00\t\x00\fascii_column\x00\x01\x00\rbigint_column\x00\x02\x00\vblob_column\x00\x03\x00\x0Eboolean_column\x00\x04\x00\x0Edecimal_column\x00\x06\x00\rdouble_column\x00\a\x00\ffloat_column\x00\b\x00\vinet_column\x00\x10\x00\nint_column\x00\t\x00\vlist_column\x00 \x00\x01\x00\nmap_column\x00!\x00\r\x00\x04\x00\nset_column\x00\"\x00\x03\x00\vtext_column\x00\r\x00\x10timestamp_column\x00\v\x00\x0Ftimeuuid_column\x00\x0F\x00\vuuid_column\x00\f\x00\x0Evarchar_column\x00\r\x00\rvarint_column\x00\x0E\x00\x00\x00\x01\x00\x00\x00\x04\x00\x00\x00\x03\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF")
241
+ response = described_class.decode!(1, buffer, buffer.length)
200
242
  response.rows.first.should eql(
201
243
  'id' => 3,
202
244
  'ascii_column' => nil,
@@ -223,19 +265,22 @@ module Cql
223
265
 
224
266
  context 'with COUNTER columns' do
225
267
  it 'decodes COUNTER as a number' do
226
- response = described_class.decode!(ByteBuffer.new("\x00\x00\x00\x01\x00\x00\x00\x03\x00\x04test\x00\x04cnts\x00\x02id\x00\r\x00\x02c1\x00\x05\x00\x02c2\x00\x05\x00\x00\x00\x01\x00\x00\x00\x04theo\x00\x00\x00\b\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\b\x00\x00\x00\x00\x00\x00\x00\x01"))
268
+ buffer = ByteBuffer.new("\x00\x00\x00\x01\x00\x00\x00\x03\x00\x04test\x00\x04cnts\x00\x02id\x00\r\x00\x02c1\x00\x05\x00\x02c2\x00\x05\x00\x00\x00\x01\x00\x00\x00\x04theo\x00\x00\x00\b\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\b\x00\x00\x00\x00\x00\x00\x00\x01")
269
+ response = described_class.decode!(1, buffer, buffer.length)
227
270
  response.rows.first['c1'].should == 3
228
271
  end
229
272
 
230
273
  it 'decodes a null COUNTER as nil' do
231
- response = described_class.decode!(ByteBuffer.new("\x00\x00\x00\x01\x00\x00\x00\x02\x00\x12cql_rb_client_spec\x00\bcounters\x00\bcounter1\x00\x05\x00\bcounter2\x00\x05\x00\x00\x00\x01\x00\x00\x00\b\x00\x00\x00\x00\x00\x00\x00\x01\xFF\xFF\xFF\xFF"))
274
+ buffer = ByteBuffer.new("\x00\x00\x00\x01\x00\x00\x00\x02\x00\x12cql_rb_client_spec\x00\bcounters\x00\bcounter1\x00\x05\x00\bcounter2\x00\x05\x00\x00\x00\x01\x00\x00\x00\b\x00\x00\x00\x00\x00\x00\x00\x01\xFF\xFF\xFF\xFF")
275
+ response = described_class.decode!(1, buffer, buffer.length)
232
276
  response.rows.first['counter2'].should be_nil
233
277
  end
234
278
  end
235
279
 
236
280
  context 'with an INET column' do
237
281
  let :response do
238
- described_class.decode!(ByteBuffer.new("\x00\x00\x00\x01\x00\x00\x00\x01\x00\ntest_types\x00\rlots_of_types\x00\vinet_column\x00\x10\x00\x00\x00\x02\x00\x00\x00\x04\x7F\x00\x00\x01\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"))
282
+ buffer = ByteBuffer.new("\x00\x00\x00\x01\x00\x00\x00\x01\x00\ntest_types\x00\rlots_of_types\x00\vinet_column\x00\x10\x00\x00\x00\x02\x00\x00\x00\x04\x7F\x00\x00\x01\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01")
283
+ described_class.decode!(1, buffer, buffer.length)
239
284
  end
240
285
 
241
286
  it 'decodes IPv4 values' do
@@ -250,21 +295,21 @@ module Cql
250
295
  context 'with an unknown column type' do
251
296
  it 'raises an error when encountering an unknown column type' do
252
297
  buffer = ByteBuffer.new("\x00\x00\x00\x01\x00\x00\x00\x03\x00\ncql_rb_328\x00\x05users\x00\tuser_name\x00\xff\x00\x05email\x00\r\x00\bpassword\x00\r\x00\x00\x00\x00")
253
- expect { described_class.decode!(buffer) }.to raise_error(UnsupportedColumnTypeError)
298
+ expect { described_class.decode!(1, buffer, buffer.length) }.to raise_error(UnsupportedColumnTypeError)
254
299
  end
255
300
  end
256
301
  end
257
302
 
258
303
  describe '#void?' do
259
304
  it 'is not void' do
260
- response = RowsResultResponse.new([{'col' => 'foo'}], [['ks', 'tbl', 'col', :varchar]], nil)
305
+ response = RowsResultResponse.new([{'col' => 'foo'}], [['ks', 'tbl', 'col', :varchar]], nil, nil)
261
306
  response.should_not be_void
262
307
  end
263
308
  end
264
309
 
265
310
  describe '#to_s' do
266
311
  it 'returns a string with metadata and rows' do
267
- response = RowsResultResponse.new([{'col' => 'foo'}], [['ks', 'tbl', 'col', :varchar]], nil)
312
+ response = RowsResultResponse.new([{'col' => 'foo'}], [['ks', 'tbl', 'col', :varchar]], nil, nil)
268
313
  response.to_s.should == 'RESULT ROWS [["ks", "tbl", "col", :varchar]] [{"col"=>"foo"}]'
269
314
  end
270
315
  end
@@ -8,7 +8,8 @@ module Cql
8
8
  describe SchemaChangeEventResponse do
9
9
  describe '.decode!' do
10
10
  let :response do
11
- described_class.decode!(ByteBuffer.new("\x00\aDROPPED\x00\ncql_rb_609\x00\x05users"))
11
+ buffer = ByteBuffer.new("\x00\aDROPPED\x00\ncql_rb_609\x00\x05users")
12
+ described_class.decode!(1, buffer, buffer.length)
12
13
  end
13
14
 
14
15
  it 'decodes the change' do
@@ -8,7 +8,8 @@ module Cql
8
8
  describe SchemaChangeResultResponse do
9
9
  describe '.decode!' do
10
10
  let :response do
11
- described_class.decode!(ByteBuffer.new("\x00\aUPDATED\x00\ncql_rb_973\x00\x05users"))
11
+ buffer = ByteBuffer.new("\x00\aUPDATED\x00\ncql_rb_973\x00\x05users")
12
+ described_class.decode!(1, buffer, buffer.length)
12
13
  end
13
14
 
14
15
  it 'decodes the description' do
@@ -8,7 +8,7 @@ module Cql
8
8
  describe SetKeyspaceResultResponse do
9
9
  describe '.decode!' do
10
10
  let :response do
11
- described_class.decode!(ByteBuffer.new("\x00\x06system"))
11
+ described_class.decode!(1, ByteBuffer.new("\x00\x06system"), 8)
12
12
  end
13
13
 
14
14
  it 'decodes the keyspace' do
@@ -8,7 +8,8 @@ module Cql
8
8
  describe StatusChangeEventResponse do
9
9
  describe '.decode!' do
10
10
  let :response do
11
- described_class.decode!(ByteBuffer.new("\x00\x04DOWN\x04\x00\x00\x00\x00\x00\x00#R"))
11
+ buffer = ByteBuffer.new("\x00\x04DOWN\x04\x00\x00\x00\x00\x00\x00#R")
12
+ described_class.decode!(1, buffer, buffer.length)
12
13
  end
13
14
 
14
15
  it 'decodes the change' do
@@ -8,7 +8,8 @@ module Cql
8
8
  describe SupportedResponse do
9
9
  describe '.decode!' do
10
10
  let :response do
11
- described_class.decode!(ByteBuffer.new("\x00\x02\x00\x0bCQL_VERSION\x00\x01\x00\x053.0.0\x00\x0bCOMPRESSION\x00\x00"))
11
+ buffer = ByteBuffer.new("\x00\x02\x00\x0bCQL_VERSION\x00\x01\x00\x053.0.0\x00\x0bCOMPRESSION\x00\x00")
12
+ described_class.decode!(1, buffer, buffer.length)
12
13
  end
13
14
 
14
15
  it 'decodes the options' do
@@ -8,7 +8,8 @@ module Cql
8
8
  describe TopologyChangeEventResponse do
9
9
  describe '.decode!' do
10
10
  let :response do
11
- described_class.decode!(ByteBuffer.new("\x00\fREMOVED_NODE\x04\x00\x00\x00\x00\x00\x00#R"))
11
+ buffer = ByteBuffer.new("\x00\fREMOVED_NODE\x04\x00\x00\x00\x00\x00\x00#R")
12
+ described_class.decode!(1, buffer, buffer.length)
12
13
  end
13
14
 
14
15
  it 'decodes the change' do
@@ -9,7 +9,7 @@ module Cql
9
9
  describe '.decode!' do
10
10
  it 'returns a new instance' do
11
11
  unused_byte_buffer = nil
12
- described_class.decode!(unused_byte_buffer).should be_a(described_class)
12
+ described_class.decode!(1, unused_byte_buffer, 0).should be_a(described_class)
13
13
  end
14
14
  end
15
15
 
@@ -14,11 +14,28 @@ module Cql
14
14
  ''
15
15
  end
16
16
 
17
- TYPES = [:ascii, :bigint, :blob, :boolean, :counter, :decimal, :double, :float, :inet, :int, :text, :varchar, :timestamp, :timeuuid, :uuid, :varint].freeze
18
-
19
17
  describe '#to_bytes' do
18
+ types = [
19
+ :ascii,
20
+ :bigint,
21
+ :blob,
22
+ :boolean,
23
+ :counter,
24
+ :decimal,
25
+ :double,
26
+ :float,
27
+ :inet,
28
+ :int,
29
+ :text,
30
+ :varchar,
31
+ :timestamp,
32
+ :timeuuid,
33
+ :uuid,
34
+ :varint
35
+ ]
36
+
20
37
  context 'when encoding normal value' do
21
- TYPES.each do |type|
38
+ types.each do |type|
22
39
  it "encodes a null #{type.upcase}" do
23
40
  converter.to_bytes(buffer, type, nil, 4).should == "\xff\xff\xff\xff"
24
41
  end
@@ -40,7 +57,7 @@ module Cql
40
57
  end
41
58
 
42
59
  context 'when encoding collection values' do
43
- TYPES.each do |type|
60
+ types.each do |type|
44
61
  it "encodes a null #{type.upcase}" do
45
62
  converter.to_bytes(buffer, type, nil, 2).should == "\xff\xff"
46
63
  end
@@ -36,8 +36,15 @@ module Cql
36
36
  Uuid.new(276263553384940695775376958868900023510).should eql(Uuid.new('cfd66ccc-d857-4e90-b1e5-df98a3d40cd6'))
37
37
  end
38
38
 
39
- it 'is not equal when anything other than a Uuid is passed' do
40
- [nil, 123, 'test'].each { |v| Uuid.new(276263553384940695775376958868900023510).should_not eql(v) }
39
+ it 'uses #value to determine equality' do
40
+ Uuid.new(276263553384940695775376958868900023510).should eql(Future.resolved(276263553384940695775376958868900023510))
41
+ end
42
+
43
+ it 'does not attempt to call #value on object that do not respond to it' do
44
+ uuid = Uuid.new(276263553384940695775376958868900023510)
45
+ expect { uuid.should_not eql(nil) }.to_not raise_error
46
+ expect { uuid.should_not eql(123) }.to_not raise_error
47
+ expect { uuid.should_not eql('test') }.to_not raise_error
41
48
  end
42
49
 
43
50
  it 'aliases #== to #eql?' do
@@ -53,7 +60,7 @@ module Cql
53
60
 
54
61
  describe '#hash' do
55
62
  it 'calculates a 64 bit hash of the UUID' do
56
- h = Uuid.new(162917432198567078063626261009205865234).hash
63
+ h = Uuid.new(276263553384940695775376958868900023510).hash
57
64
  h.should be < 2**63
58
65
  h.should be > -2**63
59
66
  end
@@ -5,7 +5,10 @@ require 'spec_helper'
5
5
 
6
6
  describe 'A CQL client' do
7
7
  let :connection_options do
8
- {:host => ENV['CASSANDRA_HOST'], :credentials => {:username => 'cassandra', :password => 'cassandra'}}
8
+ {
9
+ :host => ENV['CASSANDRA_HOST'],
10
+ :credentials => {:username => 'cassandra', :password => 'cassandra'},
11
+ }
9
12
  end
10
13
 
11
14
  let :client do
@@ -16,6 +19,18 @@ describe 'A CQL client' do
16
19
  client.close rescue nil
17
20
  end
18
21
 
22
+ def create_keyspace_and_table
23
+ begin
24
+ client.execute(%(DROP KEYSPACE cql_rb_client_spec))
25
+ rescue Cql::QueryError => e
26
+ raise e unless e.code == 0x2300
27
+ end
28
+ client.execute(%(CREATE KEYSPACE cql_rb_client_spec WITH REPLICATION = {'class': 'SimpleStrategy', 'replication_factor': 1}))
29
+ client.use('cql_rb_client_spec')
30
+ client.execute(%(CREATE TABLE users (user_id VARCHAR PRIMARY KEY, first VARCHAR, last VARCHAR, age INT)))
31
+ client.execute(%(CREATE TABLE counters (id VARCHAR PRIMARY KEY, count COUNTER)))
32
+ end
33
+
19
34
  context 'with common operations' do
20
35
  it 'executes a query and returns the result' do
21
36
  result = client.execute('SELECT * FROM system.schema_keyspaces')
@@ -47,11 +62,12 @@ describe 'A CQL client' do
47
62
 
48
63
  context 'when using prepared statements' do
49
64
  before do
50
- client.use('system')
65
+ create_keyspace_and_table
66
+ client.execute(%(UPDATE users SET first = 'Sue', last = 'Smith', age = 34 WHERE user_id = 'sue'))
51
67
  end
52
68
 
53
69
  let :statement do
54
- client.prepare('SELECT * FROM schema_keyspaces WHERE keyspace_name = ?')
70
+ client.prepare('SELECT * FROM users WHERE user_id = ?')
55
71
  end
56
72
 
57
73
  it 'prepares a statement' do
@@ -59,11 +75,39 @@ describe 'A CQL client' do
59
75
  end
60
76
 
61
77
  it 'executes a prepared statement' do
62
- result = statement.execute('system')
78
+ result = statement.execute('sue')
63
79
  result.should have(1).item
64
- result = statement.execute('system', :one)
80
+ result = statement.execute('sue', :one)
65
81
  result.should have(1).item
66
82
  end
83
+
84
+ it 'executes a prepared statement with no bound values' do
85
+ statement = client.prepare('SELECT * FROM users')
86
+ result = statement.execute(:one)
87
+ result.should_not be_empty
88
+ end
89
+
90
+ it 'executes a batch' do
91
+ statement = client.prepare('UPDATE users SET first = ?, last = ?, age = ? WHERE user_id = ?')
92
+ statement.batch do |batch|
93
+ batch.add('Sam', 'Miller', 23, 'sam')
94
+ batch.add('Kim', 'Jones', 62, 'kim')
95
+ end
96
+ result = client.execute(%(SELECT * FROM users WHERE user_id = 'kim'))
97
+ result.first['last'].should == 'Jones'
98
+ end
99
+
100
+ it 'executes a counter batch' do
101
+ statement = client.prepare('UPDATE counters SET count = count + ? WHERE id = ?')
102
+ batch = statement.batch(:counter, consistency: :quorum)
103
+ batch.add(5, 'foo')
104
+ batch.add(3, 'bar')
105
+ batch.add(6, 'foo')
106
+ batch.execute
107
+ result = client.execute('SELECT * FROM counters')
108
+ counters = result.each_with_object({}) { |row, acc| acc[row['id']] = row['count'] }
109
+ counters.should eql('foo' => 11, 'bar' => 3)
110
+ end
67
111
  end
68
112
 
69
113
  context 'with multiple connections' do
@@ -113,41 +157,88 @@ describe 'A CQL client' do
113
157
  double(:client, connect: nil, close: nil)
114
158
  end
115
159
 
116
- let :authentication_enabled do
117
- begin
118
- Cql::Client.connect(connection_options.merge(credentials: nil))
119
- false
120
- rescue Cql::AuthenticationError
121
- true
160
+ context 'and protocol v1' do
161
+ let :authentication_enabled do
162
+ begin
163
+ Cql::Client.connect(connection_options.merge(credentials: nil, protocol_version: 1))
164
+ false
165
+ rescue Cql::AuthenticationError
166
+ true
167
+ end
122
168
  end
123
- end
124
169
 
125
- it 'sends credentials given in :credentials' do
126
- client = Cql::Client.connect(connection_options.merge(credentials: {username: 'cassandra', password: 'cassandra'}))
127
- client.execute('SELECT * FROM system.schema_keyspaces')
128
- end
170
+ let :credentials do
171
+ {:username => 'cassandra', :password => 'cassandra'}
172
+ end
173
+
174
+ it 'authenticates using the credentials given in the :credentials option' do
175
+ client = Cql::Client.connect(connection_options.merge(credentials: credentials, protocol_version: 1))
176
+ client.execute('SELECT * FROM system.schema_keyspaces')
177
+ end
129
178
 
130
- it 'raises an error when no credentials have been given' do
131
- pending('authentication not configured', unless: authentication_enabled) do
132
- expect { Cql::Client.connect(connection_options.merge(credentials: nil)) }.to raise_error(Cql::AuthenticationError)
179
+ it 'raises an error when no credentials have been given' do
180
+ pending('authentication not configured', unless: authentication_enabled) do
181
+ expect { Cql::Client.connect(connection_options.merge(credentials: nil, protocol_version: 1)) }.to raise_error(Cql::AuthenticationError)
182
+ end
183
+ end
184
+
185
+ it 'raises an error when the credentials are bad' do
186
+ pending('authentication not configured', unless: authentication_enabled) do
187
+ expect {
188
+ Cql::Client.connect(connection_options.merge(credentials: {:username => 'foo', :password => 'bar'}, protocol_version: 1))
189
+ }.to raise_error(Cql::AuthenticationError)
190
+ end
191
+ end
192
+
193
+ it 'raises an error when only an auth provider has been given' do
194
+ pending('authentication not configured', unless: authentication_enabled) do
195
+ auth_provider = Cql::Client::PlainTextAuthProvider.new('cassandra', 'cassandra')
196
+ expect { Cql::Client.connect(connection_options.merge(credentials: nil, auth_provider: auth_provider, protocol_version: 1)) }.to raise_error(Cql::AuthenticationError)
197
+ end
133
198
  end
134
199
  end
135
200
 
136
- it 'raises an error when the credentials are bad' do
137
- pending('authentication not configured', unless: authentication_enabled) do
138
- expect {
139
- Cql::Client.connect(connection_options.merge(credentials: {username: 'foo', password: 'bar'}))
140
- }.to raise_error(Cql::AuthenticationError)
201
+ context 'and protocol v2' do
202
+ let :authentication_enabled do
203
+ begin
204
+ Cql::Client.connect(connection_options.merge(auth_provider: nil, credentials: nil))
205
+ false
206
+ rescue Cql::AuthenticationError
207
+ true
208
+ end
209
+ end
210
+
211
+ it 'uses the auth provider given in the :auth_provider option' do
212
+ auth_provider = Cql::Client::PlainTextAuthProvider.new('cassandra', 'cassandra')
213
+ client = Cql::Client.connect(connection_options.merge(auth_provider: auth_provider, credentials: nil))
214
+ client.execute('SELECT * FROM system.schema_keyspaces')
215
+ end
216
+
217
+ it 'falls back on creating a PlainTextAuthProvider using the credentials given in the :credentials option' do
218
+ client = Cql::Client.connect(connection_options.merge(auth_provider: nil, credentials: {:username => 'cassandra', :password => 'cassandra'}))
219
+ client.execute('SELECT * FROM system.schema_keyspaces')
220
+ end
221
+
222
+ it 'raises an error when no auth provider or credentials have been given' do
223
+ pending('authentication not configured', unless: authentication_enabled) do
224
+ expect { Cql::Client.connect(connection_options.merge(auth_provider: nil, credentials: nil)) }.to raise_error(Cql::AuthenticationError)
225
+ end
226
+ end
227
+
228
+ it 'raises an error when the credentials are bad' do
229
+ pending('authentication not configured', unless: authentication_enabled) do
230
+ expect {
231
+ auth_provider = Cql::Client::PlainTextAuthProvider.new('foo', 'bar')
232
+ Cql::Client.connect(connection_options.merge(auth_provider: auth_provider, credentials: nil))
233
+ }.to raise_error(Cql::AuthenticationError)
234
+ end
141
235
  end
142
236
  end
143
237
  end
144
238
 
145
239
  context 'with tracing' do
146
240
  before do
147
- client.execute(%(DROP KEYSPACE cql_rb_client_spec)) rescue nil
148
- client.execute(%(CREATE KEYSPACE cql_rb_client_spec WITH REPLICATION = {'class': 'SimpleStrategy', 'replication_factor': 1}))
149
- client.use('cql_rb_client_spec')
150
- client.execute(%(CREATE TABLE users (user_id VARCHAR PRIMARY KEY, first VARCHAR, last VARCHAR, age INT)))
241
+ create_keyspace_and_table
151
242
  end
152
243
 
153
244
  it 'requests tracing and returns the trace ID, for row returning operations' do
@@ -187,6 +278,138 @@ describe 'A CQL client' do
187
278
  end
188
279
  end
189
280
 
281
+ context 'with on-the-fly bound variables' do
282
+ before do
283
+ create_keyspace_and_table
284
+ end
285
+
286
+ it 'executes a query and sends the values separately' do
287
+ result = client.execute(%<INSERT INTO users (user_id, first, last) VALUES (?, ?, ?)>, 'sue', 'Sue', 'Smith')
288
+ result.should be_empty
289
+ end
290
+
291
+ it 'encodes the values using the provided type hints' do
292
+ result = client.execute(%<INSERT INTO users (user_id, first, last, age) VALUES (?, ?, ?, ?)>, 'sue', 'Sue', 'Smith', 35, type_hints: [nil, nil, nil, :int])
293
+ result.should be_empty
294
+ end
295
+ end
296
+
297
+ context 'when batching operations' do
298
+ before do
299
+ create_keyspace_and_table
300
+ end
301
+
302
+ it 'sends the operations as a single request' do
303
+ batch = client.batch
304
+ batch.add(%(UPDATE users SET last = 'Smith' WHERE user_id = 'smith'))
305
+ batch.add(%(UPDATE users SET last = 'Jones' WHERE user_id = 'jones'))
306
+ batch.execute
307
+ result = client.execute(%(SELECT * FROM users WHERE user_id = 'jones'))
308
+ result.first.should include('last' => 'Jones')
309
+ end
310
+
311
+ it 'accepts prepared statements' do
312
+ prepared_statement = client.prepare(%(UPDATE users SET last = ? WHERE user_id = ?))
313
+ batch = client.batch
314
+ batch.add(prepared_statement, 'Smith', 'smith')
315
+ batch.add(prepared_statement, 'Jones', 'jones')
316
+ batch.execute
317
+ result = client.execute(%(SELECT * FROM users WHERE user_id = 'jones'))
318
+ result.first.should include('last' => 'Jones')
319
+ end
320
+
321
+ it 'accepts a mix of prepared, regular and statements with on-the-fly bound variables' do
322
+ prepared_statement = client.prepare(%(UPDATE users SET last = ? WHERE user_id = ?))
323
+ batch = client.batch
324
+ batch.add(prepared_statement, 'Smith', 'smith')
325
+ batch.add(%(UPDATE users SET last = 'Jones' WHERE user_id = 'jones'))
326
+ batch.add(%(UPDATE users SET last = ?, age = ? WHERE user_id = ?), 'Taylor', 53, 'taylor', type_hints: [nil, :int, nil])
327
+ batch.execute
328
+ result = client.execute(%(SELECT * FROM users WHERE user_id = 'jones'))
329
+ result.first.should include('last' => 'Jones')
330
+ result = client.execute(%(SELECT * FROM users WHERE user_id = 'taylor'))
331
+ result.first.should include('last' => 'Taylor')
332
+ end
333
+
334
+ it 'yields the batch to a block and executes it afterwards' do
335
+ client.batch do |batch|
336
+ batch.add(%(UPDATE users SET last = 'Jones' WHERE user_id = 'jones'))
337
+ end
338
+ result = client.execute(%(SELECT * FROM users WHERE user_id = 'jones'))
339
+ result.first.should include('last' => 'Jones')
340
+ end
341
+
342
+ it 'can be used for counter increments' do
343
+ client.batch(:counter) do |batch|
344
+ batch.add(%(UPDATE counters SET count = count + 1 WHERE id = 'foo'))
345
+ batch.add(%(UPDATE counters SET count = count + 2 WHERE id = 'bar'))
346
+ batch.add(%(UPDATE counters SET count = count + 3 WHERE id = 'baz'))
347
+ end
348
+ result = client.execute('SELECT * FROM counters')
349
+ counters = result.each_with_object({}) { |row, acc| acc[row['id']] = row['count'] }
350
+ counters.should eql('foo' => 1, 'bar' => 2, 'baz' => 3)
351
+ end
352
+
353
+ it 'can be unlogged' do
354
+ client.batch(:unlogged) do |batch|
355
+ batch.add(%(UPDATE users SET last = 'Jones' WHERE user_id = 'jones'))
356
+ end
357
+ result = client.execute(%(SELECT * FROM users WHERE user_id = 'jones'))
358
+ result.first.should include('last' => 'Jones')
359
+ end
360
+
361
+ it 'can be traced' do
362
+ batch = client.batch(:unlogged)
363
+ batch.add(%(UPDATE users SET last = 'Jones' WHERE user_id = 'jones'))
364
+ result = batch.execute(trace: true)
365
+ result.trace_id.should_not be_nil
366
+ result = client.batch(:unlogged, trace: true) do |batch|
367
+ batch.add(%(UPDATE users SET last = 'Jones' WHERE user_id = 'jones'))
368
+ end
369
+ result.trace_id.should_not be_nil
370
+ end
371
+ end
372
+
373
+ context 'when paging large result sets' do
374
+ let :row_count do
375
+ 200
376
+ end
377
+
378
+ before do
379
+ create_keyspace_and_table
380
+ statement = client.prepare('UPDATE counters SET count = count + ? WHERE id = ?')
381
+ ids = Set.new
382
+ ids << rand(234234).to_s(36) until ids.size == row_count
383
+ ids = ids.to_a
384
+ client.batch(:counter) do |batch|
385
+ row_count.times do |i|
386
+ batch.add(statement, rand(234), ids[i])
387
+ end
388
+ end
389
+ end
390
+
391
+ it 'returns the first page, and a way to retrieve the next when using #execute' do
392
+ page_size = row_count/2 + 10
393
+ result_page = client.execute('SELECT * FROM counters', page_size: page_size)
394
+ result_page.count.should == page_size
395
+ result_page.should_not be_last_page
396
+ result_page = result_page.next_page
397
+ result_page.count.should == row_count - page_size
398
+ result_page.should be_last_page
399
+ end
400
+
401
+ it 'returns the first page, and a way to retrieve the next when using a prepared statement' do
402
+ page_size = row_count/2 + 10
403
+ statement = client.prepare('SELECT * FROM counters')
404
+ result_page = statement.execute(page_size: page_size)
405
+ result_page.count.should == page_size
406
+ result_page.should_not be_last_page
407
+ result_page = result_page.next_page
408
+ result_page.count.should == row_count - page_size
409
+ result_page.should be_last_page
410
+ end
411
+ end
412
+
190
413
  context 'with error conditions' do
191
414
  it 'raises an error for CQL syntax errors' do
192
415
  expect { client.execute('BAD cql') }.to raise_error(Cql::CqlError)