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