cassandra-driver 3.0.0 → 3.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- YjE4OWU5NjliOWY2YjgwM2RkMzJlNmMyZWU0YzdlOWFlYzNkYjUxOQ==
4
+ MjRlMWI4NGUyYzNjNzMzZTQ1MTNiNDE4NTIxODk3NGIwOTIwZjVkZg==
5
5
  data.tar.gz: !binary |-
6
- M2E5YTgyMTllNTg2ZGI2NjgyN2VjZTJmN2Q0MGI4ZDAwNGU1YjkxMw==
6
+ YWMwYmNlM2RmYzIzYWY5MGQ2MmIyNjU5NTU3NTkyNGMxYmY0NTAyNA==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- ZTBmMzYzMTQwMDEwYTQ4YjljNmNhMWQwMGI0ZWZjYTVlOTczMjkxNzUzNTMz
10
- YzY5MzhjNjUyMjc2Mjc2NGVhODE4NDYwOTRiOTViZWY5MGE4YmQ1NTg1MGY0
11
- MTYwZDgwZDg2MDQzNTViNmI0NDk2ZWRjMWRhMWMxZTQ5MTVkODQ=
9
+ NTdhMWZiODYzYTViZGJiNTY5NDYzMjQ2ZDZiMDE2MTY4MTI3NTY3NjM0Y2Rl
10
+ YmE0MjkxYmZhNDI0OTliMDcyZWUyMjM3M2E2OWIzNzg2MGJkODVlMjhmYmQ3
11
+ N2NhYzQ5NmY2MmQxYzdkY2U3NTJmZDAyMjI2MWE4MTM3YTZkMmY=
12
12
  data.tar.gz: !binary |-
13
- ZmQ5MGM4ZTI1ZTFmN2QzNjE5OGNmNjg4MzVmZTJkYjM0NTM0MzI4MWIxNGJk
14
- MDY3NTRmOWQ0YWM5NWUzY2Q2NTFmOWIwOGExZDI5MWQ0ODIyOWVmMjVkNjll
15
- MGRhMWJkOGExZThlNWQzOWMzYTE1YzZmNTM5YWJjZmNhMTllNWE=
13
+ Y2IwOGMwOGQzYTlkZjUzYjQ1NzFlZjJhNTY3YWM2MmNjN2QxM2ExZmU0YWZh
14
+ ODBjYjQ4ODUwZDAzMTZjMzRhMDEwYzhkZDA4Zjk3NjIzOWI1OWM2ZGViYjQ0
15
+ ZjZkZjM0YTEzNTY0YTAxNzA0YWEwYjdlOWY2NjkyOWNkZDE4YjM=
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Datastax Ruby Driver for Apache Cassandra
2
2
 
3
- *If you're reading this on GitHub, please note that this is the readme for the development version and that some features described here might not yet have been released. You can [find the documentation for latest version through ruby driver docs](http://datastax.github.io/ruby-driver/) or via the release tags, [e.g. v3.0.0](https://github.com/datastax/ruby-driver/tree/v3.0.0).*
3
+ *If you're reading this on GitHub, please note that this is the readme for the development version and that some features described here might not yet have been released. You can [find the documentation for the latest version through ruby driver docs](http://docs.datastax.com/en/latest-ruby-driver/ruby-driver/whatsNew.html) or via the release tags, [e.g. v3.0.2](https://github.com/datastax/ruby-driver/tree/v3.0.2).*
4
4
 
5
5
  [![Build Status](https://travis-ci.org/datastax/ruby-driver.svg?branch=master)](https://travis-ci.org/datastax/ruby-driver)
6
6
 
@@ -8,7 +8,7 @@ A Ruby client driver for Apache Cassandra. This driver works exclusively with
8
8
  the Cassandra Query Language version 3 (CQL3) and Cassandra's native protocol.
9
9
 
10
10
  - Code: https://github.com/datastax/ruby-driver
11
- - Docs: http://datastax.github.io/ruby-driver/
11
+ - Docs: http://docs.datastax.com/en/latest-ruby-driver/ruby-driver/whatsNew.html
12
12
  - Jira: https://datastax-oss.atlassian.net/browse/RUBY
13
13
  - Mailing List: https://groups.google.com/a/lists.datastax.com/forum/#!forum/ruby-driver-user
14
14
  - IRC: #datastax-drivers on [irc.freenode.net](http://freenode.net>)
@@ -16,14 +16,14 @@ the Cassandra Query Language version 3 (CQL3) and Cassandra's native protocol.
16
16
 
17
17
  This driver is based on [the cql-rb gem](https://github.com/iconara/cql-rb) by [Theo Hultberg](https://github.com/iconara) and we added support for:
18
18
 
19
- * [Asynchronous execution](http://datastax.github.io/ruby-driver/features/asynchronous_io/)
20
- * One-off, [prepared](http://datastax.github.io/ruby-driver/features/basics/prepared_statements/) and [batch statements](http://datastax.github.io/ruby-driver/features/basics/batch_statements/)
21
- * Automatic peer discovery and cluster metadata with [support for change notifications](http://datastax.github.io/ruby-driver/features/state_listeners/)
22
- * Various [load-balancing](http://datastax.github.io/ruby-driver/features/load_balancing/), [retry](http://datastax.github.io/ruby-driver/features/retry_policies/) and [reconnection](http://datastax.github.io/ruby-driver/features/reconnection/) policies with [ability to write your own](http://datastax.github.io/ruby-driver/features/load_balancing/implementing_a_policy/)
23
- * [SSL encryption](http://datastax.github.io/ruby-driver/features/security/ssl_encryption/)
24
- * [Flexible and robust error handling](http://datastax.github.io/ruby-driver/features/error_handling/)
25
- * [Per-request execution information and tracing](http://datastax.github.io/ruby-driver/features/debugging/)
26
- * [Configurable address resolution](http://datastax.github.io/ruby-driver/features/address_resolution/)
19
+ * [Asynchronous execution](http://docs.datastax.com/en/developer/ruby-driver/3.0/supplemental/features/asynchronous_io/)
20
+ * One-off, [prepared](http://docs.datastax.com/en/developer/ruby-driver/3.0/supplemental/features/basics/prepared_statements/) and [batch statements](http://docs.datastax.com/en/developer/ruby-driver/3.0/supplemental/features/basics/batch_statements/)
21
+ * Automatic peer discovery and cluster metadata with [support for change notifications](http://docs.datastax.com/en/developer/ruby-driver/3.0/supplemental/features/state_listeners/)
22
+ * Various [load-balancing](http://docs.datastax.com/en/developer/ruby-driver/3.0/supplemental/features/load_balancing/), [retry](http://docs.datastax.com/en/developer/ruby-driver/3.0/supplemental/features/retry_policies/) and [reconnection](http://docs.datastax.com/en/developer/ruby-driver/3.0/supplemental/features/reconnection/) policies with [ability to write your own](http://docs.datastax.com/en/developer/ruby-driver/3.0/supplemental/features/load_balancing/implementing_a_policy/)
23
+ * [SSL encryption](http://docs.datastax.com/en/developer/ruby-driver/3.0/supplemental/features/security/ssl_encryption/)
24
+ * [Flexible and robust error handling](http://docs.datastax.com/en/developer/ruby-driver/3.0/supplemental/features/error_handling/)
25
+ * [Per-request execution information and tracing](http://docs.datastax.com/en/developer/ruby-driver/3.0/supplemental/features/debugging/)
26
+ * [Configurable address resolution](http://docs.datastax.com/en/developer/ruby-driver/3.0/supplemental/features/address_resolution/)
27
27
 
28
28
  [Check out the slides from Ruby Driver Explained](https://speakerdeck.com/avalanche123/ruby-driver-explained) for a detailed overview of the Ruby Driver architecture.
29
29
 
@@ -67,9 +67,9 @@ __Note__: The host you specify is just a seed node, the driver will automaticall
67
67
 
68
68
  Read more:
69
69
 
70
- * [`Cassandra.cluster` options](http://datastax.github.io/ruby-driver/api/#cluster-class_method)
71
- * [`Session#execute_async` options](http://datastax.github.io/ruby-driver/api/session/#execute_async-instance_method)
72
- * [Usage documentation](http://datastax.github.io/ruby-driver/features)
70
+ * [`Cassandra.cluster` options](http://docs.datastax.com/en/developer/ruby-driver/3.0/supplemental/api/cassandra/#cluster-class_method)
71
+ * [`Session#execute_async` options](http://docs.datastax.com/en/developer/ruby-driver/3.0/supplemental/api/cassandra/session/#execute_async-instance_method)
72
+ * [Usage documentation](http://docs.datastax.com/en/developer/ruby-driver/3.0/supplemental/features)
73
73
 
74
74
  ## Installation
75
75
 
@@ -85,14 +85,16 @@ Install via Gemfile
85
85
  gem 'cassandra-driver'
86
86
  ```
87
87
 
88
- __Note__: if you want to use compression you should also install [snappy](http://rubygems.org/gems/snappy) or [lz4-ruby](http://rubygems.org/gems/lz4-ruby). [Read more about compression.](http://datastax.github.io/ruby-driver/features/#compression)
88
+ __Note__: if you want to use compression you should also install [snappy](http://rubygems.org/gems/snappy) or [lz4-ruby](http://rubygems.org/gems/lz4-ruby). [Read more about compression.](http://docs.datastax.com/en/developer/ruby-driver/3.0/supplemental/features/#compression)
89
89
 
90
90
 
91
91
  ## Upgrading from cql-rb
92
92
 
93
- Some of the new features added to the driver have unfortunately led to changes in the original cql-rb API. In the examples directory, you can find [an example of how to wrap the ruby driver to achieve almost complete interface parity with cql-rb](https://github.com/datastax/ruby-driver/blob/master/examples/cql-rb-wrapper.rb) to assist you with gradual upgrade.
93
+ Some of the new features added to the driver have unfortunately led to changes in the original cql-rb API. In the examples directory, you can find [an example of how to wrap the ruby driver to achieve almost complete interface parity with cql-rb](https://github.com/datastax/ruby-driver/blob/v3.0.2/examples/cql-rb-wrapper.rb) to assist you with gradual upgrade.
94
94
 
95
- ## What's new in v3.0.0
95
+ ## What's new in v3.0
96
+
97
+ See the [changelog](https://github.com/datastax/ruby-driver/blob/v3.0.2/CHANGELOG.md) for details on patch versions.
96
98
 
97
99
  ### Features:
98
100
 
@@ -162,7 +164,7 @@ examples in the `features/` directory.
162
164
  ## Running tests
163
165
 
164
166
  If you don't feel like reading through the following instructions on how to run
165
- ruby-driver tests, feel free to [check out .travis.yml for the entire build code](https://github.com/datastax/ruby-driver/blob/master/.travis.yml).
167
+ ruby-driver tests, feel free to [check out .travis.yml for the entire build code](https://github.com/datastax/ruby-driver/blob/v3.0.2/.travis.yml).
166
168
 
167
169
  * Check out the driver codebase and install test dependencies:
168
170
 
@@ -186,7 +188,7 @@ CASSANDRA_VERSION=2.0.17 bundle exec rake test # run both as well as integration
186
188
  ## Changelog & versioning
187
189
 
188
190
  Check out the [releases on GitHub](https://github.com/datastax/ruby-driver/releases) and
189
- [changelog](https://github.com/datastax/ruby-driver/blob/master/CHANGELOG.md). Version
191
+ [changelog](https://github.com/datastax/ruby-driver/blob/v3.0.2/CHANGELOG.md). Version
190
192
  numbering follows the [semantic versioning](http://semver.org/) scheme.
191
193
 
192
194
  Private and experimental APIs, defined as whatever is not in the
@@ -210,7 +212,7 @@ the release.
210
212
  * Because the driver reactor is using `IO.select`, the maximum number of tcp connections allowed is 1024.
211
213
  * Because the driver uses `IO#write_nonblock`, Windows is not supported.
212
214
 
213
- Please [refer to the usage documentation for more information on common pitfalls](http://datastax.github.io/ruby-driver/features/)
215
+ Please [refer to the usage documentation for more information on common pitfalls](http://docs.datastax.com/en/developer/ruby-driver/3.0/supplemental/features/)
214
216
 
215
217
  ## Contributing
216
218
 
@@ -240,4 +242,4 @@ License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
240
242
  either express or implied. See the License for the specific language governing permissions
241
243
  and limitations under the License.
242
244
 
243
- [1]: http://datastax.github.io/ruby-driver/api
245
+ [1]: http://docs.datastax.com/en/developer/ruby-driver/3.0/supplemental/api
data/lib/cassandra.rb CHANGED
@@ -66,6 +66,7 @@ module Cassandra
66
66
  :connections_per_remote_node,
67
67
  :consistency,
68
68
  :credentials,
69
+ :custom_types,
69
70
  :datacenter,
70
71
  :futures_factory,
71
72
  :heartbeat_interval,
@@ -293,28 +294,7 @@ module Cassandra
293
294
  # @return [Cassandra::Future<Cassandra::Cluster>] a future resolving to the
294
295
  # cluster instance.
295
296
  def self.cluster_async(options = {})
296
- options = validate_and_massage_options(options)
297
- hosts = []
298
-
299
- Array(options.fetch(:hosts, '127.0.0.1')).each do |host|
300
- case host
301
- when ::IPAddr
302
- hosts << host
303
- when ::String # ip address or hostname
304
- Resolv.each_address(host) do |ip|
305
- hosts << ::IPAddr.new(ip)
306
- end
307
- else
308
- raise ::ArgumentError, ":hosts must be String or IPAddr, #{host.inspect} given"
309
- end
310
- end
311
-
312
- if hosts.empty?
313
- raise ::ArgumentError,
314
- ":hosts #{options[:hosts].inspect} could not be resolved to any ip address"
315
- end
316
-
317
- hosts.shuffle!
297
+ options, hosts = validate_and_massage_options(options)
318
298
  rescue => e
319
299
  futures = options.fetch(:futures_factory) { return Future::Error.new(e) }
320
300
  futures.error(e)
@@ -664,14 +644,14 @@ module Cassandra
664
644
 
665
645
  case address_resolution
666
646
  when :none
667
- # do nothing
647
+ # do nothing
668
648
  when :ec2_multi_region
669
649
  options[:address_resolution_policy] =
670
650
  AddressResolution::Policies::EC2MultiRegion.new
671
651
  else
672
652
  raise ::ArgumentError,
673
653
  ':address_resolution must be either :none or :ec2_multi_region, ' \
674
- "#{address_resolution.inspect} given"
654
+ "#{address_resolution.inspect} given"
675
655
  end
676
656
  end
677
657
 
@@ -757,7 +737,31 @@ module Cassandra
757
737
  end
758
738
  end
759
739
  end
760
- options
740
+
741
+ # Get host addresses.
742
+ hosts = []
743
+
744
+ Array(options.fetch(:hosts, '127.0.0.1')).each do |host|
745
+ case host
746
+ when ::IPAddr
747
+ hosts << host
748
+ when ::String # ip address or hostname
749
+ Resolv.each_address(host) do |ip|
750
+ hosts << ::IPAddr.new(ip)
751
+ end
752
+ else
753
+ raise ::ArgumentError, ":hosts must be String or IPAddr, #{host.inspect} given"
754
+ end
755
+ end
756
+
757
+ if hosts.empty?
758
+ raise ::ArgumentError,
759
+ ":hosts #{options[:hosts].inspect} could not be resolved to any ip address"
760
+ end
761
+
762
+ hosts.shuffle!
763
+
764
+ [options, hosts]
761
765
  end
762
766
 
763
767
  # @private
@@ -798,6 +802,7 @@ require 'cassandra/null_logger'
798
802
  require 'cassandra/executors'
799
803
  require 'cassandra/future'
800
804
  require 'cassandra/cluster'
805
+ require 'cassandra/custom_data'
801
806
  require 'cassandra/driver'
802
807
  require 'cassandra/host'
803
808
  require 'cassandra/session'
@@ -831,6 +836,11 @@ require 'cassandra/util'
831
836
  # murmur3 hash extension
832
837
  require 'cassandra_murmur3'
833
838
 
839
+ # SortedSet has a race condition where it does some class/global initialization when the first instance is created.
840
+ # If this is done in a multi-threaded environment, bad things can happen. So force the initialization here,
841
+ # when loading the C* module.
842
+ ::SortedSet.new
843
+
834
844
  module Cassandra
835
845
  # @private
836
846
  VOID_STATEMENT = Statements::Void.new
@@ -34,7 +34,7 @@ module Cassandra
34
34
  #
35
35
  # @see Cassandra::Auth::Providers
36
36
  class Provider
37
- # @!method create_authenticator(authentication_class, protocol_version)
37
+ # @!method create_authenticator(authentication_class, host)
38
38
  #
39
39
  # Create a new authenticator object. This method will be called once per
40
40
  # connection that requires authentication. The auth provider can create
@@ -45,6 +45,8 @@ module Cassandra
45
45
  #
46
46
  # @param authentication_class [String] the authentication class used by
47
47
  # the server.
48
+ # @param host [Cassandra::Host] the node to whom we're authenticating.
49
+ #
48
50
  # @return [Cassandra::Auth::Authenticator, nil] an object with an
49
51
  # interface matching {Cassandra::Auth::Authenticator} or `nil` if the
50
52
  # authentication class is not supported.
@@ -1390,38 +1390,44 @@ module Cassandra
1390
1390
  end
1391
1391
  else
1392
1392
  response_future.on_failure do |ex|
1393
- errors[host] = ex
1394
- case request
1395
- when Protocol::QueryRequest, Protocol::PrepareRequest
1396
- send_request_by_plan(promise,
1397
- keyspace,
1398
- statement,
1399
- options,
1400
- request,
1401
- plan,
1402
- timeout,
1403
- errors,
1404
- hosts)
1405
- when Protocol::ExecuteRequest
1406
- execute_by_plan(promise,
1407
- keyspace,
1408
- statement,
1409
- options,
1410
- request,
1411
- plan,
1412
- timeout,
1413
- errors,
1414
- hosts)
1415
- when Protocol::BatchRequest
1416
- batch_by_plan(promise,
1417
- keyspace,
1418
- statement,
1419
- options,
1420
- request,
1421
- plan,
1422
- timeout,
1423
- errors,
1424
- hosts)
1393
+ if ex.is_a?(Errors::HostError) ||
1394
+ (ex.is_a?(Errors::TimeoutError) && statement.idempotent?)
1395
+
1396
+ errors[host] = ex
1397
+ case request
1398
+ when Protocol::QueryRequest, Protocol::PrepareRequest
1399
+ send_request_by_plan(promise,
1400
+ keyspace,
1401
+ statement,
1402
+ options,
1403
+ request,
1404
+ plan,
1405
+ timeout,
1406
+ errors,
1407
+ hosts)
1408
+ when Protocol::ExecuteRequest
1409
+ execute_by_plan(promise,
1410
+ keyspace,
1411
+ statement,
1412
+ options,
1413
+ request,
1414
+ plan,
1415
+ timeout,
1416
+ errors,
1417
+ hosts)
1418
+ when Protocol::BatchRequest
1419
+ batch_by_plan(promise,
1420
+ keyspace,
1421
+ statement,
1422
+ options,
1423
+ request,
1424
+ plan,
1425
+ timeout,
1426
+ errors,
1427
+ hosts)
1428
+ else
1429
+ promise.break(ex)
1430
+ end
1425
1431
  else
1426
1432
  promise.break(ex)
1427
1433
  end
@@ -134,7 +134,8 @@ module Cassandra
134
134
  @connection_options.compressor,
135
135
  @connection_options.heartbeat_interval,
136
136
  @connection_options.idle_timeout,
137
- @connection_options.requests_per_connection)
137
+ @connection_options.requests_per_connection,
138
+ @connection_options.custom_type_handlers)
138
139
  end.flat_map do |connection|
139
140
  # connection is a CqlProtocolHandler
140
141
  f = request_options(connection)
@@ -154,7 +155,7 @@ module Cassandra
154
155
  supported_cql_versions.first :
155
156
  '3.1.0'
156
157
 
157
- startup_connection(connection, cql_version, compression)
158
+ startup_connection(host, connection, cql_version, compression)
158
159
  end
159
160
  f.fallback do |error|
160
161
  case error
@@ -200,7 +201,7 @@ module Cassandra
200
201
  end
201
202
  end
202
203
 
203
- def startup_connection(connection, cql_version, compression)
204
+ def startup_connection(host, connection, cql_version, compression)
204
205
  connection.send_request(Protocol::StartupRequest.new(cql_version, compression),
205
206
  @execution_options.timeout).flat_map do |r|
206
207
  case r
@@ -213,12 +214,9 @@ module Cassandra
213
214
  Ione::Future.failed(cannot_authenticate_error)
214
215
  end
215
216
  else
216
- authenticator = @connection_options.create_authenticator(
217
- r.authentication_class)
217
+ authenticator = @connection_options.create_authenticator(r.authentication_class, host)
218
218
  if authenticator
219
- challenge_response_cycle(connection,
220
- authenticator,
221
- authenticator.initial_response)
219
+ challenge_response_cycle(connection, authenticator, authenticator.initial_response)
222
220
  else
223
221
  Ione::Future.failed(cannot_authenticate_error)
224
222
  end
@@ -283,7 +281,7 @@ module Cassandra
283
281
  case r
284
282
  when Protocol::AuthChallengeResponse
285
283
  token = authenticator.challenge_response(r.token)
286
- challenge_response_cycle(pending_connection, authenticator, token)
284
+ challenge_response_cycle(connection, authenticator, token)
287
285
  when Protocol::AuthSuccessResponse
288
286
  begin
289
287
  authenticator.authentication_successful(r.token)
@@ -24,7 +24,7 @@ module Cassandra
24
24
 
25
25
  attr_reader :auth_provider, :compressor, :connect_timeout, :credentials,
26
26
  :heartbeat_interval, :idle_timeout, :port, :schema_refresh_delay,
27
- :schema_refresh_timeout, :ssl
27
+ :schema_refresh_timeout, :ssl, :custom_type_handlers
28
28
  attr_boolean :protocol_negotiable, :synchronize_schema, :nodelay
29
29
 
30
30
  attr_accessor :protocol_version
@@ -45,7 +45,8 @@ module Cassandra
45
45
  schema_refresh_delay,
46
46
  schema_refresh_timeout,
47
47
  nodelay,
48
- requests_per_connection)
48
+ requests_per_connection,
49
+ custom_types)
49
50
  @logger = logger
50
51
  @protocol_version = protocol_version
51
52
  @credentials = credentials
@@ -60,6 +61,10 @@ module Cassandra
60
61
  @schema_refresh_delay = schema_refresh_delay
61
62
  @schema_refresh_timeout = schema_refresh_timeout
62
63
  @nodelay = nodelay
64
+ @custom_type_handlers = {}
65
+ custom_types.each do |type_klass|
66
+ @custom_type_handlers[type_klass.type] = type_klass
67
+ end
63
68
 
64
69
  @connections_per_local_node = connections_per_local_node
65
70
  @connections_per_remote_node = connections_per_remote_node
@@ -78,8 +83,17 @@ module Cassandra
78
83
  @compressor && @compressor.algorithm
79
84
  end
80
85
 
81
- def create_authenticator(authentication_class)
82
- @auth_provider && @auth_provider.create_authenticator(authentication_class)
86
+ def create_authenticator(authentication_class, host)
87
+ if @auth_provider
88
+ # Auth providers should take an auth-class and host, but they used to not, so for backward compatibility
89
+ # we figure out if this provider does, and if so send both args, otherwise just send the auth-class.
90
+
91
+ if @auth_provider.method(:create_authenticator).arity == 1
92
+ @auth_provider.create_authenticator(authentication_class)
93
+ else
94
+ @auth_provider.create_authenticator(authentication_class, host)
95
+ end
96
+ end
83
97
  end
84
98
 
85
99
  def connections_per_local_node
@@ -72,6 +72,9 @@ module Cassandra
72
72
  Cassandra::Types.tuple(*node.children.map { |t| lookup_type(t, types)})
73
73
  when 'empty' then
74
74
  Cassandra::Types.custom('org.apache.cassandra.db.marshal.EmptyType')
75
+ when /\A'/ then
76
+ # Custom type.
77
+ Cassandra::Types.custom(node.name[1..-2])
75
78
  else
76
79
  types.fetch(node.name) do
77
80
  raise IncompleteTypeError, "unable to lookup type #{node.name.inspect}"
@@ -836,7 +836,7 @@ module Cassandra
836
836
  initial_state = Util.encode_object(
837
837
  Protocol::Coder.read_value_v4(
838
838
  Protocol::CqlByteBuffer.new.append_bytes(aggregate_data['initcond']),
839
- state_type))
839
+ state_type, nil))
840
840
 
841
841
  # The state-function takes arguments: first the stype, then the args of the aggregate.
842
842
  state_function = functions.get(aggregate_data['state_func'],
@@ -0,0 +1,51 @@
1
+ # encoding: utf-8
2
+
3
+ #--
4
+ # Copyright 2013-2016 DataStax, Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #++
18
+
19
+ # Use this module to mark domain object classes as custom type implementations for custom-type
20
+ # columns in C*. This module has no logic of its own, but indicates that the marked class has
21
+ # certain methods.
22
+ # @private
23
+ module Cassandra::CustomData
24
+ def self.included base
25
+ base.send :include, InstanceMethods
26
+ base.extend ClassMethods
27
+ end
28
+
29
+ module ClassMethods
30
+ # @return [Cassandra::Types::Custom] the custom type that this class represents.
31
+ def type
32
+ raise NotImplementedError, "#{self.class} must implement the :type class method"
33
+ end
34
+
35
+ # Deserialize the given data into an instance of this domain object class.
36
+ # @param data [String] byte-array representation of a column value of this custom type.
37
+ # @return An instance of the domain object class.
38
+ # @raise [Cassandra::Errors::DecodingError] upon failure.
39
+ def deserialize(data)
40
+ raise NotImplementedError, "#{self.class} must implement the :deserialize class method"
41
+ end
42
+ end
43
+
44
+ module InstanceMethods
45
+ # Serialize this domain object into a byte array to send to C*.
46
+ # @return [String] byte-array representation of this domain object.
47
+ def serialize
48
+ raise NotImplementedError, "#{self.class} must implement the :serialize instance method"
49
+ end
50
+ end
51
+ end
@@ -92,23 +92,25 @@ module Cassandra
92
92
  schema_fetcher)
93
93
  end
94
94
 
95
+ let(:cluster_klass) { Cluster }
96
+
95
97
  let(:cluster) do
96
- Cluster.new(logger,
97
- io_reactor,
98
- executor,
99
- control_connection,
100
- cluster_registry,
101
- cluster_schema,
102
- cluster_metadata,
103
- execution_options,
104
- connection_options,
105
- load_balancing_policy,
106
- reconnection_policy,
107
- retry_policy,
108
- address_resolution_policy,
109
- connector,
110
- futures_factory,
111
- timestamp_generator)
98
+ cluster_klass.new(logger,
99
+ io_reactor,
100
+ executor,
101
+ control_connection,
102
+ cluster_registry,
103
+ cluster_schema,
104
+ cluster_metadata,
105
+ execution_options,
106
+ connection_options,
107
+ load_balancing_policy,
108
+ reconnection_policy,
109
+ retry_policy,
110
+ address_resolution_policy,
111
+ connector,
112
+ futures_factory,
113
+ timestamp_generator)
112
114
  end
113
115
 
114
116
  let(:execution_options) do
@@ -137,10 +139,12 @@ module Cassandra
137
139
  schema_refresh_delay,
138
140
  schema_refresh_timeout,
139
141
  nodelay,
140
- requests_per_connection
142
+ requests_per_connection,
143
+ custom_types
141
144
  )
142
145
  end
143
146
 
147
+ let(:custom_types) { [] }
144
148
  let(:port) { 9042 }
145
149
  let(:protocol_version) { nil }
146
150
  let(:connect_timeout) { 10 }
@@ -177,7 +177,7 @@ module Cassandra
177
177
  @consistency = consistency || trusted_options.consistency
178
178
  @page_size = page_size || trusted_options.page_size
179
179
  @trace = trace.nil? ? trusted_options.trace? : !!trace
180
- @timeout = timeout || trusted_options.timeout
180
+ @timeout = options.key?(:timeout) ? timeout : trusted_options.timeout
181
181
  @serial_consistency = serial_consistency || trusted_options.serial_consistency
182
182
  @arguments = arguments || trusted_options.arguments
183
183
  @type_hints = type_hints || trusted_options.type_hints
@@ -28,6 +28,13 @@ module Cassandra
28
28
 
29
29
  BYTES_FORMAT = 'C*'.freeze
30
30
  TWO_INTS_FORMAT = 'NN'.freeze
31
+
32
+ # All of the formats above are big-endian (e.g. network-byte-order). Some payloads (custom types) may have
33
+ # little-endian components.
34
+
35
+ DOUBLE_FORMAT_LE = 'E'.freeze
36
+ INT_FORMAT_LE = 'V'.freeze
37
+ SHORT_FORMAT_LE = 'v'.freeze
31
38
  end
32
39
 
33
40
  module Constants
@@ -97,6 +97,7 @@ module Cassandra
97
97
  when :bigint, :counter then write_bigint(buffer, value)
98
98
  when :blob then write_blob(buffer, value)
99
99
  when :boolean then write_boolean(buffer, value)
100
+ when :custom then write_custom(buffer, value, type)
100
101
  when :decimal then write_decimal(buffer, value)
101
102
  when :double then write_double(buffer, value)
102
103
  when :float then write_float(buffer, value)
@@ -221,19 +222,19 @@ module Cassandra
221
222
  end
222
223
  end
223
224
 
224
- def read_values_v4(buffer, column_metadata)
225
+ def read_values_v4(buffer, column_metadata, custom_type_handlers)
225
226
  ::Array.new(buffer.read_int) do |_i|
226
227
  row = ::Hash.new
227
228
 
228
229
  column_metadata.each do |(_, _, column, type)|
229
- row[column] = read_value_v4(buffer, type)
230
+ row[column] = read_value_v4(buffer, type, custom_type_handlers)
230
231
  end
231
232
 
232
233
  row
233
234
  end
234
235
  end
235
236
 
236
- def read_value_v4(buffer, type)
237
+ def read_value_v4(buffer, type, custom_type_handlers)
237
238
  case type.kind
238
239
  when :ascii then read_ascii(buffer)
239
240
  when :bigint, :counter then read_bigint(buffer)
@@ -253,11 +254,12 @@ module Cassandra
253
254
  when :smallint then read_smallint(buffer)
254
255
  when :time then read_time(buffer)
255
256
  when :date then read_date(buffer)
257
+ when :custom then read_custom(buffer, type, custom_type_handlers)
256
258
  when :list
257
259
  return nil unless read_size(buffer)
258
260
 
259
261
  value_type = type.value_type
260
- ::Array.new(buffer.read_signed_int) { read_value_v4(buffer, value_type) }
262
+ ::Array.new(buffer.read_signed_int) { read_value_v4(buffer, value_type, custom_type_handlers) }
261
263
  when :map
262
264
  return nil unless read_size(buffer)
263
265
 
@@ -266,7 +268,7 @@ module Cassandra
266
268
  value = ::Hash.new
267
269
 
268
270
  buffer.read_signed_int.times do
269
- value[read_value_v4(buffer, key_type)] = read_value_v4(buffer, value_type)
271
+ value[read_value_v4(buffer, key_type, custom_type_handlers)] = read_value_v4(buffer, value_type, custom_type_handlers)
270
272
  end
271
273
 
272
274
  value
@@ -277,7 +279,7 @@ module Cassandra
277
279
  value = ::Set.new
278
280
 
279
281
  buffer.read_signed_int.times do
280
- value << read_value_v4(buffer, value_type)
282
+ value << read_value_v4(buffer, value_type, custom_type_handlers)
281
283
  end
282
284
 
283
285
  value
@@ -295,7 +297,7 @@ module Cassandra
295
297
  values[field.name] = if length - buffer.length >= size
296
298
  nil
297
299
  else
298
- read_value_v4(buffer, field.type)
300
+ read_value_v4(buffer, field.type, custom_type_handlers)
299
301
  end
300
302
  end
301
303
 
@@ -308,7 +310,7 @@ module Cassandra
308
310
 
309
311
  members.each do |member_type|
310
312
  break if buffer.empty?
311
- values << read_value_v4(buffer, member_type)
313
+ values << read_value_v4(buffer, member_type, custom_type_handlers)
312
314
  end
313
315
 
314
316
  values.fill(nil, values.length, (members.length - values.length))
@@ -774,6 +776,15 @@ module Cassandra
774
776
  read_size(buffer) && buffer.read(1) == Constants::TRUE_BYTE
775
777
  end
776
778
 
779
+ def read_custom(buffer, type, custom_type_handlers)
780
+ # Lookup the type-name to get the Class that can deserialize buffer data into a custom domain object.
781
+ unless custom_type_handlers && custom_type_handlers.key?(type)
782
+ raise Errors::DecodingError, %(Unsupported custom column type: #{type.name})
783
+ end
784
+ num_bytes = read_size(buffer)
785
+ custom_type_handlers[type].deserialize(buffer.read(num_bytes)) if num_bytes && num_bytes > 0
786
+ end
787
+
777
788
  def read_decimal(buffer)
778
789
  size = read_size(buffer)
779
790
  size && buffer.read_decimal(size)
@@ -860,6 +871,15 @@ module Cassandra
860
871
  buffer.append(value ? Constants::TRUE_BYTE : Constants::FALSE_BYTE)
861
872
  end
862
873
 
874
+ def write_custom(buffer, value, type)
875
+ # Verify that the given type-name matches the value's cql type name.
876
+ if value.class.type != type
877
+ raise Errors::EncodingError, "type mismatch: value is a #{value.type} and column is a #{type}"
878
+ end
879
+
880
+ buffer.append_bytes(value.serialize)
881
+ end
882
+
863
883
  def write_decimal(buffer, value)
864
884
  buffer.append_bytes(CqlByteBuffer.new.append_decimal(value))
865
885
  end
@@ -92,6 +92,13 @@ module Cassandra
92
92
  "Not enough bytes available to decode a double: #{e.message}", e.backtrace
93
93
  end
94
94
 
95
+ def read_double_le
96
+ read(8).unpack(Formats::DOUBLE_FORMAT_LE).first
97
+ rescue RangeError => e
98
+ raise Errors::DecodingError,
99
+ "Not enough bytes available to decode a double: #{e.message}", e.backtrace
100
+ end
101
+
95
102
  def read_float
96
103
  read(4).unpack(Formats::FLOAT_FORMAT).first
97
104
  rescue RangeError => e
@@ -108,6 +115,13 @@ module Cassandra
108
115
  "Not enough bytes available to decode an int: #{e.message}", e.backtrace
109
116
  end
110
117
 
118
+ def read_unsigned_int_le
119
+ read(4).unpack(Formats::INT_FORMAT_LE).first
120
+ rescue RangeError => e
121
+ raise Errors::DecodingError,
122
+ "Not enough bytes available to decode an int: #{e.message}", e.backtrace
123
+ end
124
+
111
125
  def read_unsigned_short
112
126
  read_short
113
127
  rescue RangeError => e
@@ -115,6 +129,13 @@ module Cassandra
115
129
  "Not enough bytes available to decode a short: #{e.message}", e.backtrace
116
130
  end
117
131
 
132
+ def read_unsigned_short_le
133
+ read(2).unpack(Formats::SHORT_FORMAT_LE).first
134
+ rescue RangeError => e
135
+ raise Errors::DecodingError,
136
+ "Not enough bytes available to decode a short: #{e.message}", e.backtrace
137
+ end
138
+
118
139
  def read_string
119
140
  length = read_unsigned_short
120
141
  string = read(length)
@@ -46,7 +46,8 @@ module Cassandra
46
46
  compressor = nil,
47
47
  heartbeat_interval = 30,
48
48
  idle_timeout = 60,
49
- requests_per_connection = 128)
49
+ requests_per_connection = 128,
50
+ custom_type_handlers = {})
50
51
  @protocol_version = protocol_version
51
52
  @connection = connection
52
53
  @scheduler = scheduler
@@ -60,7 +61,7 @@ module Cassandra
60
61
 
61
62
  if protocol_version > 3
62
63
  @frame_encoder = V4::Encoder.new(@compressor, protocol_version)
63
- @frame_decoder = V4::Decoder.new(self, @compressor)
64
+ @frame_decoder = V4::Decoder.new(self, @compressor, custom_type_handlers)
64
65
  elsif protocol_version > 2
65
66
  @frame_encoder = V3::Encoder.new(@compressor, protocol_version)
66
67
  @frame_decoder = V3::Decoder.new(self, @compressor)
@@ -24,17 +24,19 @@ module Cassandra
24
24
  protocol_version,
25
25
  raw_rows,
26
26
  paging_state,
27
- trace_id)
27
+ trace_id,
28
+ custom_type_handlers = nil)
28
29
  super(custom_payload, warnings, nil, nil, paging_state, trace_id)
29
30
  @protocol_version = protocol_version
30
31
  @raw_rows = raw_rows
32
+ @custom_type_handlers = custom_type_handlers
31
33
  end
32
34
 
33
35
  def materialize(metadata)
34
36
  @metadata = metadata
35
37
 
36
38
  @rows = if @protocol_version == 4
37
- Coder.read_values_v4(@raw_rows, @metadata)
39
+ Coder.read_values_v4(@raw_rows, @metadata, @custom_type_handlers)
38
40
  elsif @protocol_version == 3
39
41
  Coder.read_values_v3(@raw_rows, @metadata)
40
42
  else
@@ -241,7 +241,8 @@ module Cassandra
241
241
  protocol_version,
242
242
  remaining_bytes,
243
243
  paging_state,
244
- trace_id)
244
+ trace_id,
245
+ nil)
245
246
  else
246
247
  RowsResultResponse.new(nil,
247
248
  nil,
@@ -275,7 +275,8 @@ module Cassandra
275
275
  protocol_version,
276
276
  remaining_bytes,
277
277
  paging_state,
278
- trace_id)
278
+ trace_id,
279
+ nil)
279
280
  else
280
281
  RowsResultResponse.new(nil,
281
282
  nil,
@@ -59,7 +59,7 @@ module Cassandra
59
59
  end
60
60
 
61
61
  class Decoder
62
- def initialize(handler, compressor = nil)
62
+ def initialize(handler, compressor = nil, custom_type_handlers = {})
63
63
  @handler = handler
64
64
  @compressor = compressor
65
65
  @state = :initial
@@ -68,6 +68,7 @@ module Cassandra
68
68
  @code = nil
69
69
  @length = nil
70
70
  @buffer = CqlByteBuffer.new
71
+ @custom_type_handlers = custom_type_handlers
71
72
  end
72
73
 
73
74
  def <<(data)
@@ -325,11 +326,12 @@ module Cassandra
325
326
  protocol_version,
326
327
  remaining_bytes,
327
328
  paging_state,
328
- trace_id)
329
+ trace_id,
330
+ @custom_type_handlers)
329
331
  else
330
332
  RowsResultResponse.new(custom_payload,
331
333
  warnings,
332
- Coder.read_values_v4(buffer, column_specs),
334
+ Coder.read_values_v4(buffer, column_specs, @custom_type_handlers),
333
335
  column_specs,
334
336
  paging_state,
335
337
  trace_id)
@@ -76,8 +76,8 @@ module Cassandra
76
76
  #
77
77
  # @note `:paging_state` option will be ignored.
78
78
  #
79
- # @return [Cassandra::Future<Cassandra::Result, nil>] `nil` if last
80
- # page
79
+ # @return [Cassandra::Future<Cassandra::Result>] a future that resolves to a new Result if there is a new page,
80
+ # `nil` otherwise.
81
81
  #
82
82
  # @see Cassandra::Session#execute
83
83
  def next_page_async(options = nil)
@@ -89,20 +89,13 @@ module Cassandra
89
89
 
90
90
  case statement
91
91
  when ::String
92
- @client.query(Statements::Simple.new(statement,
93
- options.arguments,
94
- options.type_hints,
95
- options.idempotent?),
96
- options)
97
- when Statements::Simple
98
- @client.query(statement, options)
99
- when Statements::Prepared
100
- @client.execute(statement.bind(options.arguments), options)
101
- when Statements::Bound
102
- @client.execute(statement, options)
103
- when Statements::Batch
104
- Util.assert_not_empty(statement.statements) { 'batch cannot be empty' }
105
- @client.batch(statement, options)
92
+ Statements::Simple.new(statement,
93
+ options.arguments,
94
+ options.type_hints,
95
+ options.idempotent?).accept(@client,
96
+ options)
97
+ when Statement
98
+ statement.accept(@client, options)
106
99
  else
107
100
  @futures.error(::ArgumentError.new("unsupported statement #{statement.inspect}"))
108
101
  end
@@ -23,5 +23,10 @@ module Cassandra
23
23
  def idempotent?
24
24
  !!@idempotent
25
25
  end
26
+
27
+ # @private
28
+ def accept(client, options)
29
+ raise NotImplementedError, "#{self.class} must implement :accept method"
30
+ end
26
31
  end
27
32
  end
@@ -113,6 +113,12 @@ module Cassandra
113
113
  self
114
114
  end
115
115
 
116
+ # @private
117
+ def accept(client, options)
118
+ Util.assert_not_empty(statements) { 'batch cannot be empty' }
119
+ client.batch(self, options)
120
+ end
121
+
116
122
  # Determines whether or not the statement is safe to retry on timeout
117
123
  # Batches are idempotent only when all statements in a batch are.
118
124
  # @return [Boolean] whether the statement is safe to retry on timeout
@@ -46,6 +46,11 @@ module Cassandra
46
46
  @idempotent = idempotent
47
47
  end
48
48
 
49
+ # @private
50
+ def accept(client, options)
51
+ client.execute(self, options)
52
+ end
53
+
49
54
  # @return [String] a CLI-friendly bound statement representation
50
55
  def inspect
51
56
  "#<#{self.class.name}:0x#{object_id.to_s(16)} @cql=#{@cql.inspect} " \
@@ -155,6 +155,11 @@ module Cassandra
155
155
  nil)
156
156
  end
157
157
 
158
+ # @private
159
+ def accept(client, options)
160
+ client.execute(bind(options.arguments), options)
161
+ end
162
+
158
163
  # @return [String] a CLI-friendly prepared statement representation
159
164
  def inspect
160
165
  "#<#{self.class.name}:0x#{object_id.to_s(16)} @cql=#{@cql.inspect}>"
@@ -180,7 +185,9 @@ module Cassandra
180
185
  'the partition key and must be present.'
181
186
  end
182
187
 
183
- if @connection_options.protocol_version >= 3
188
+ if @connection_options.protocol_version >= 4
189
+ Protocol::Coder.write_value_v4(buffer, value, type)
190
+ elsif @connection_options.protocol_version >= 3
184
191
  Protocol::Coder.write_value_v3(buffer, value, type)
185
192
  else
186
193
  Protocol::Coder.write_value_v1(buffer, value, type)
@@ -200,7 +207,9 @@ module Cassandra
200
207
  'the partition key and must be present.'
201
208
  end
202
209
 
203
- if @connection_options.protocol_version >= 3
210
+ if @connection_options.protocol_version >= 4
211
+ Protocol::Coder.write_value_v4(buf, value, type)
212
+ elsif @connection_options.protocol_version >= 3
204
213
  Protocol::Coder.write_value_v3(buf, value, type)
205
214
  else
206
215
  Protocol::Coder.write_value_v1(buf, value, type)
@@ -93,6 +93,11 @@ module Cassandra
93
93
  @idempotent = idempotent
94
94
  end
95
95
 
96
+ # @private
97
+ def accept(client, options)
98
+ client.query(self, options)
99
+ end
100
+
96
101
  # @return [String] a CLI-friendly simple statement representation
97
102
  def inspect
98
103
  "#<#{self.class.name}:0x#{object_id.to_s(16)} @cql=#{@cql.inspect} " \
@@ -23,6 +23,11 @@ module Cassandra
23
23
  class Void
24
24
  include Statement
25
25
 
26
+ # @private
27
+ def accept(client, options)
28
+ nil
29
+ end
30
+
26
31
  # Returns nothing
27
32
  # @return [nil] there is no cql for the void statement
28
33
  def cql
@@ -55,7 +55,7 @@ module Cassandra
55
55
  end
56
56
 
57
57
  def inspect
58
- "#<Cassandra::Tuple:0x#{object_id.to_s(16)} #{self}>"
58
+ "#<Cassandra::Tuple:0x#{object_id.to_s(16)} types=#{@types.inspect}, []=#{@values.inspect}>"
59
59
  end
60
60
  end
61
61
 
@@ -114,7 +114,7 @@ module Cassandra
114
114
 
115
115
  # @private
116
116
  def inspect
117
- "#<Cassandra::Tuple:0x#{object_id.to_s(16)} #{self}>"
117
+ "#<Cassandra::Tuple:0x#{object_id.to_s(16)} []=#{@values.inspect}>"
118
118
  end
119
119
 
120
120
  # @private
@@ -1389,8 +1389,8 @@ module Cassandra
1389
1389
  # @raise [ArgumentError] if the value is invalid
1390
1390
  # @return [void]
1391
1391
  def assert(value, message = nil, &block)
1392
- raise ::NotImplementedError,
1393
- "unable to assert a value for custom type: #{@name.inspect}"
1392
+ Util.assert_instance_of(CustomData, value, message, &block)
1393
+ Util.assert_equal(self, value.class.type, message, &block)
1394
1394
  end
1395
1395
 
1396
1396
  # @return [String] a cassandra representation of this type
@@ -196,6 +196,7 @@ module Cassandra
196
196
  Types.udt(object.keyspace, object.name, object.types)
197
197
  when UDT
198
198
  Types.udt('unknown', 'unknown', object.map {|k, v| [k, guess_type(v)]})
199
+ when Cassandra::CustomData then object.class.type
199
200
  else
200
201
  raise ::ArgumentError,
201
202
  "Unable to guess the type of the argument: #{object.inspect}"
@@ -17,5 +17,5 @@
17
17
  #++
18
18
 
19
19
  module Cassandra
20
- VERSION = '3.0.0'.freeze
20
+ VERSION = '3.0.2'.freeze
21
21
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cassandra-driver
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0
4
+ version: 3.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Theo Hultberg
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2016-05-23 00:00:00.000000000 Z
13
+ date: 2016-06-27 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: ione
@@ -107,6 +107,7 @@ files:
107
107
  - lib/cassandra/compression.rb
108
108
  - lib/cassandra/compression/compressors/lz4.rb
109
109
  - lib/cassandra/compression/compressors/snappy.rb
110
+ - lib/cassandra/custom_data.rb
110
111
  - lib/cassandra/driver.rb
111
112
  - lib/cassandra/errors.rb
112
113
  - lib/cassandra/execution/info.rb