cassandra-driver 3.0.0.rc.2 → 3.0.0

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.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- ZTBiZDQ4YTM2MDliN2U0N2VlNmJjY2RiYjQyOWVlOWI4MzlhNjNkZg==
4
+ YjE4OWU5NjliOWY2YjgwM2RkMzJlNmMyZWU0YzdlOWFlYzNkYjUxOQ==
5
5
  data.tar.gz: !binary |-
6
- OGY1MDNjN2Y5ZmFjYmY2YWY0ZWI0ZGNjZjViMTA1ZDdkYzQyOTg0Yg==
6
+ M2E5YTgyMTllNTg2ZGI2NjgyN2VjZTJmN2Q0MGI4ZDAwNGU1YjkxMw==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- MTIzOTk1Nzk0NDI2NWNkZWNiZmUyMmIzZjk4YjQ2YmRjZDkwODc4YjUxY2Fj
10
- N2FmYjRlOGZiMjBlNTAzY2RmOGM4OGNlZmZkZWVjN2RjNDU1ZjY4MjYxODE3
11
- YWU1MTZiNWMyMjViOTVhODExODlkNmQwMzc1ODZlMjkwYWU0NDQ=
9
+ ZTBmMzYzMTQwMDEwYTQ4YjljNmNhMWQwMGI0ZWZjYTVlOTczMjkxNzUzNTMz
10
+ YzY5MzhjNjUyMjc2Mjc2NGVhODE4NDYwOTRiOTViZWY5MGE4YmQ1NTg1MGY0
11
+ MTYwZDgwZDg2MDQzNTViNmI0NDk2ZWRjMWRhMWMxZTQ5MTVkODQ=
12
12
  data.tar.gz: !binary |-
13
- ZDI1ZDVmMWQyNzJiMGRiYzc5NTgxYjE4ZGEwNjVmZWM0Nzc0Yzk4MGJjODcx
14
- YjEzYjNlNTZhOTU1YWY2M2JlZGUxZDA1YWU3NjRlMGE4ZmM4ODUyMTgyNmVj
15
- NjU3NWUzYzFiMDNmOGJlZDg1ODJiYzJkMDQ3OGZhNjU3MWZlMTY=
13
+ ZmQ5MGM4ZTI1ZTFmN2QzNjE5OGNmNjg4MzVmZTJkYjM0NTM0MzI4MWIxNGJk
14
+ MDY3NTRmOWQ0YWM5NWUzY2Q2NTFmOWIwOGExZDI5MWQ0ODIyOWVmMjVkNjll
15
+ MGRhMWJkOGExZThlNWQzOWMzYTE1YzZmNTM5YWJjZmNhMTllNWE=
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-rc.2](https://github.com/datastax/ruby-driver/tree/v3.0.0-rc.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).*
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
 
@@ -31,8 +31,8 @@ This driver is based on [the cql-rb gem](https://github.com/iconara/cql-rb) by [
31
31
 
32
32
  This driver works exclusively with the Cassandra Query Language v3 (CQL3) and Cassandra's native protocol. The current version works with:
33
33
 
34
- * Apache Cassandra versions 1.2, 2.0, 2.1, and 3.x
35
- * DataStax Enterprise 4.0, 4.5, 4.6, 4.7, and 4.8
34
+ * Apache Cassandra versions 1.2, 2.0, 2.1, 2.2, and 3.x
35
+ * DataStax Enterprise 4.0 and above.
36
36
  * Ruby (MRI) 2.2, 2.3
37
37
  * JRuby 1.7
38
38
 
@@ -96,28 +96,33 @@ Some of the new features added to the driver have unfortunately led to changes i
96
96
 
97
97
  ### Features:
98
98
 
99
- * Apache Cassandra native protocol v4
100
- * Add support for smallint, tinyint, date (Cassandra::Date) and time (Cassandra::Time) data types.
99
+ * Add support for Apache Cassandra native protocol v4
100
+ * Add support for smallint, tinyint, date (`Cassandra::Date`) and time (`Cassandra::Time`) data types.
101
101
  * Include schema metadata for User Defined Functions and User Defined Aggregates.
102
+ * Augment the `Cassandra::Table` object to expose many more attributes: `id`, `options`, `keyspace`, `partition_key`, `clustering_columns`, and `clustering_order`. This makes it significantly easier to write administration scripts that report various attributes of your schema, which may help to highlight areas for improvement.
102
103
  * Include client ip addresses in request traces, only on Cassandra 3.x.
103
- * Add new retry policy decision Cassandra::Retry::Policy#try_next_host.
104
- * Support specifying statement idempotence with the new :idempotent option when executing.
105
- * Support sending custom payloads when preparing or executing statements using the new :payload option.
106
- * Expose custom payloads received with responses on server exceptions and Cassandra::Execution::Info instances.
107
- * Expose server warnings on server exceptions and Cassandra::Execution::Info instances.
108
- * Add connections_per_local_node, connections_per_remote_node, requests_per_connection cluster configuration options to tune parallel query execution and resource usage.
109
- * Add Cassandra::Logger class to make it easy for users to enable debug logging in the client.
110
- * Add protocol_version configuration option to allow the user to force the protocol version to use for communication with nodes.
104
+ * Add new retry policy decision `Cassandra::Retry::Policy#try_next_host`.
105
+ * Support specifying statement idempotence with the new `idempotent` option when executing.
106
+ * Support sending custom payloads when preparing or executing statements using the new `payload` option.
107
+ * Expose custom payloads received with responses on server exceptions and `Cassandra::Execution::Info` instances.
108
+ * Expose server warnings on server exceptions and `Cassandra::Execution::Info` instances.
109
+ * Add `connections_per_local_node`, `connections_per_remote_node`, `requests_per_connection` cluster configuration options to tune parallel query execution and resource usage.
110
+ * Add `Cassandra::Logger` class to make it easy for users to enable debug logging in the client.
111
+ * Add `protocol_version` configuration option to allow the user to force the protocol version to use for communication with nodes.
111
112
  * Add support for materialized views and indexes in the schema metadata.
113
+ * Support the `ReadError`, `WriteError`, and `FunctionCallError` Cassandra error responses introduced in Cassandra 2.2.
114
+ * Add support for unset variables in bound statements.
115
+ * Support DSE security (`DseAuthenticator`, configured for LDAP).
116
+ * Add a timeout option to `Cassandra::Future#get`.
112
117
 
113
- ### Breaking Changes:
118
+ ### Breaking Changes from 2.x:
114
119
 
115
- * Cassandra::Future#join is now an alias to Cassandra::Future#get and will raise an error if the future is resolved with one.
120
+ * `Cassandra::Future#join` is now an alias to Cassandra::Future#get and will raise an error if the future is resolved with one.
116
121
  * Default consistency level is now LOCAL_ONE.
117
122
  * Enable tcp no-delay by default.
118
123
  * Unavailable errors are retried on the next host in the load balancing plan by default.
119
- * Statement execution no longer retried on timeouts, unless :idempotent => true has been specified when executing.
120
- * Cassandra::Statements::Batch#add signature has changed in how one specifies query parameters. Specify the query parameters array as the value of the arguments key:
124
+ * Statement execution no longer retried on timeouts, unless the statement is marked as idempotent in the call to `Cassandra::Session#execute*` or when creating a `Cassandra::Statement` object.
125
+ * `Cassandra::Statements::Batch#add` and `Cassandra::Session#execute*` signatures have changed in how one specifies query parameters. Specify the query parameters array as the value of the arguments key:
121
126
 
122
127
  ```ruby
123
128
  batch.add(query, ['val1', 'val2'])
@@ -128,6 +133,10 @@ batch.add(query, {p1: 'val1'})
128
133
  # becomes
129
134
  batch.add(query, arguments: {p1: 'val1'})
130
135
  ```
136
+ * The Datacenter-aware load balancing policy (`Cassandra::LoadBalancing::Policies::DCAwareRoundRobin`) defaults to using
137
+ nodes in the local DC only. In prior releases, the policy would fall back to remote nodes after exhausting local nodes.
138
+ Specify a positive value (or nil for unlimited) for `max_remote_hosts_to_use` when initializing the policy to allow remote node use.
139
+ * Unspecified variables in statements previously resulted in an exception. Now they are essentially ignored or treated as null.
131
140
 
132
141
  ### Bug Fixes:
133
142
 
@@ -135,12 +144,13 @@ batch.add(query, arguments: {p1: 'val1'})
135
144
  * [[RUBY-143](https://datastax-oss.atlassian.net/browse/RUBY-143)] Retry querying system table for metadata of new hosts when prior attempts fail, ultimately enabling use of new hosts.
136
145
  * [[RUBY-150](https://datastax-oss.atlassian.net/browse/RUBY-150)] Fixed a protocol decoding error that occurred when multiple messages are available in a stream.
137
146
  * [[RUBY-151](https://datastax-oss.atlassian.net/browse/RUBY-151)] Decode incomplete UDTs properly.
138
- * [[RUBY-161](https://datastax-oss.atlassian.net/browse/RUBY-161)] Protocol version negotiation in mixed version clusters should not fall back to v1 unless it is truly warranted.
147
+ * [[RUBY-155](https://datastax-oss.atlassian.net/browse/RUBY-155)] Request timeout timer should not include request queuing time.
148
+ * [[RUBY-161](https://datastax-oss.atlassian.net/browse/RUBY-161)] Protocol version negotiation in mixed version clusters should not fall back to v1 unless it is truly warranted.
149
+ * [[RUBY-214](https://datastax-oss.atlassian.net/browse/RUBY-214)] Ensure client timestamps have microsecond precision in JRuby. Previously, some row updates would get lost in high transaction environments.
139
150
 
140
151
  ## Feedback Requested
141
152
 
142
- *Help us focus our efforts!* [Provide your input](http://goo.gl/forms/pCs8PTpHLf)
143
- on the Ruby Driver Platform and Runtime Survey (we kept it short).
153
+ *Help us focus our efforts!* [Provide your input](http://goo.gl/forms/pCs8PTpHLf) on the Ruby Driver Platform and Runtime Survey (we kept it short).
144
154
 
145
155
  ## Code examples
146
156
 
@@ -187,14 +187,17 @@ module Cassandra
187
187
  # v2 or earlier protocol.
188
188
  #
189
189
  # @option options [Integer] :protocol_version (nil) Version of protocol to speak to
190
- # nodes. By default, this is auto-negotiated to the lowest common protocol version
190
+ # nodes. By default, this is auto-negotiated to the highest common protocol version
191
191
  # that all nodes in `:hosts` speak.
192
192
  #
193
- # @option options [Boolean] :client_timestamps (false) whether the driver
194
- # should send timestamps for each executed statement. Enabling this setting
195
- # allows mitigating Cassandra cluster clock skew because the timestamp of
196
- # the client machine will be used. This does not help mitigate application
197
- # cluster clock skew.
193
+ # @option options [Boolean, Cassandra::TimestampGenerator] :client_timestamps (false) whether the driver
194
+ # should send timestamps for each executed statement and possibly which timestamp generator to use. Enabling this
195
+ # setting helps mitigate Cassandra cluster clock skew because the timestamp of the client machine will be used.
196
+ # This does not help mitigate application cluster clock skew. Also accepts an initialized
197
+ # {Cassandra::TimestampGenerator}, `:simple` (indicating an instance of {Cassandra::TimestampGenerator::Simple}),
198
+ # or `:monotonic` (indicating an instance of {Cassandra::TimestampGenerator::TickingOnDuplicate}). If set to true,
199
+ # it defaults to {Cassandra::TimestampGenerator::Simple} for all Ruby flavors except JRuby. On JRuby, it defaults to
200
+ # {Cassandra::TimestampGenerator::TickingOnDuplicate}.
198
201
  #
199
202
  # @option options [Boolean] :synchronize_schema (true) whether the driver
200
203
  # should automatically keep schema metadata synchronized. When enabled, the
@@ -682,7 +685,35 @@ module Cassandra
682
685
  end
683
686
 
684
687
  options[:synchronize_schema] = !!options[:synchronize_schema] if options.key?(:synchronize_schema)
685
- options[:client_timestamps] = !!options[:client_timestamps] if options.key?(:client_timestamps)
688
+
689
+ if options.key?(:client_timestamps)
690
+ timestamp_generator = case options[:client_timestamps]
691
+ when true
692
+ if RUBY_ENGINE == 'jruby'
693
+ Cassandra::TimestampGenerator::TickingOnDuplicate.new
694
+ else
695
+ Cassandra::TimestampGenerator::Simple.new
696
+ end
697
+ when false
698
+ nil
699
+ when :simple
700
+ Cassandra::TimestampGenerator::Simple.new
701
+ when :monotonic
702
+ Cassandra::TimestampGenerator::TickingOnDuplicate.new
703
+ else
704
+ # The value must be a generator instance.
705
+ options[:client_timestamps]
706
+ end
707
+
708
+ if timestamp_generator
709
+ Util.assert_responds_to(:next, timestamp_generator) do
710
+ ":client_timestamps #{options[:client_timestamps].inspect} must be a boolean, :simple, :monotonic, or " \
711
+ 'an object that responds to :next'
712
+ end
713
+ end
714
+ options.delete(:client_timestamps)
715
+ options[:timestamp_generator] = timestamp_generator
716
+ end
686
717
 
687
718
  if options.key?(:connections_per_local_node)
688
719
  connections_per_node = options[:connections_per_local_node]
@@ -793,6 +824,7 @@ require 'cassandra/load_balancing'
793
824
  require 'cassandra/reconnection'
794
825
  require 'cassandra/retry'
795
826
  require 'cassandra/address_resolution'
827
+ require 'cassandra/timestamp_generator'
796
828
 
797
829
  require 'cassandra/util'
798
830
 
@@ -41,7 +41,8 @@ module Cassandra
41
41
  retry_policy,
42
42
  address_resolution_policy,
43
43
  connector,
44
- futures_factory)
44
+ futures_factory,
45
+ timestamp_generator)
45
46
  @logger = logger
46
47
  @io_reactor = io_reactor
47
48
  @executor = executor
@@ -57,6 +58,7 @@ module Cassandra
57
58
  @address_resolver = address_resolution_policy
58
59
  @connector = connector
59
60
  @futures = futures_factory
61
+ @timestamp_generator = timestamp_generator
60
62
 
61
63
  @control_connection.on_close do |_cause|
62
64
  begin
@@ -204,7 +206,8 @@ module Cassandra
204
206
  @retry_policy,
205
207
  @address_resolver,
206
208
  @connection_options,
207
- @futures)
209
+ @futures,
210
+ @timestamp_generator)
208
211
  session = Session.new(client, @execution_options, @futures)
209
212
  promise = @futures.promise
210
213
 
@@ -275,7 +278,15 @@ module Cassandra
275
278
 
276
279
  # @private
277
280
  def inspect
278
- "#<#{self.class.name}:0x#{object_id.to_s(16)}>"
281
+ "#<#{self.class.name}:0x#{object_id.to_s(16)} " \
282
+ "name=#{name.inspect}, " \
283
+ "port=#{@connection_options.port}, " \
284
+ "protocol_version=#{@connection_options.protocol_version}, " \
285
+ "load_balancing_policy=#{@load_balancing_policy.inspect}, " \
286
+ "consistency=#{@execution_options.consistency.inspect}, " \
287
+ "timeout=#{@execution_options.timeout.inspect}, " \
288
+ "hosts=#{hosts.inspect}, " \
289
+ "keyspaces=#{keyspaces.inspect}>"
279
290
  end
280
291
  end
281
292
  end
@@ -34,7 +34,8 @@ module Cassandra
34
34
  retry_policy,
35
35
  address_resolution_policy,
36
36
  connection_options,
37
- futures_factory)
37
+ futures_factory,
38
+ timestamp_generator)
38
39
  @logger = logger
39
40
  @registry = cluster_registry
40
41
  @schema = cluster_schema
@@ -52,6 +53,7 @@ module Cassandra
52
53
  @pending_connections = ::Hash.new
53
54
  @keyspace = nil
54
55
  @state = :idle
56
+ @timestamp_generator = timestamp_generator
55
57
 
56
58
  mon_initialize
57
59
  end
@@ -228,11 +230,7 @@ module Cassandra
228
230
  'Apache Cassandra'))
229
231
  end
230
232
 
231
- timestamp = nil
232
- if @connection_options.client_timestamps? &&
233
- @connection_options.protocol_version > 2
234
- timestamp = ::Time.now
235
- end
233
+ timestamp = @timestamp_generator.next if @timestamp_generator && @connection_options.protocol_version > 2
236
234
  payload = nil
237
235
  payload = options.payload if @connection_options.protocol_version >= 4
238
236
  request = Protocol::QueryRequest.new(statement.cql,
@@ -286,11 +284,7 @@ module Cassandra
286
284
  end
287
285
 
288
286
  def execute(statement, options)
289
- timestamp = nil
290
- if @connection_options.client_timestamps? &&
291
- @connection_options.protocol_version > 2
292
- timestamp = ::Time.now
293
- end
287
+ timestamp = @timestamp_generator.next if @timestamp_generator && @connection_options.protocol_version > 2
294
288
  payload = nil
295
289
  payload = options.payload if @connection_options.protocol_version >= 4
296
290
  timeout = options.timeout
@@ -324,11 +318,7 @@ module Cassandra
324
318
  'Apache Cassandra'))
325
319
  end
326
320
 
327
- timestamp = nil
328
- if @connection_options.client_timestamps? &&
329
- @connection_options.protocol_version > 2
330
- timestamp = ::Time.now
331
- end
321
+ timestamp = @timestamp_generator.next if @timestamp_generator && @connection_options.protocol_version > 2
332
322
  payload = nil
333
323
  payload = options.payload if @connection_options.protocol_version >= 4
334
324
  timeout = options.timeout
@@ -644,7 +634,15 @@ module Cassandra
644
634
  errors,
645
635
  hosts)
646
636
  cql = statement.cql
647
- id = synchronize { @prepared_statements[host][cql] }
637
+ id = nil
638
+ host_is_up = true
639
+ synchronize do
640
+ if @prepared_statements[host].nil?
641
+ host_is_up = false
642
+ else
643
+ id = @prepared_statements[host][cql]
644
+ end
645
+ end
648
646
 
649
647
  if id
650
648
  request.id = id
@@ -659,6 +657,19 @@ module Cassandra
659
657
  timeout,
660
658
  errors,
661
659
  hosts)
660
+ elsif !host_is_up
661
+ # We've hit a race condition where the plan says we can query this host, but the host has gone
662
+ # down in the mean time. Just execute the plan again on the next host.
663
+ @logger.debug("#{host} is down; executing plan on next host")
664
+ execute_by_plan(promise,
665
+ keyspace,
666
+ statement,
667
+ options,
668
+ request,
669
+ plan,
670
+ timeout,
671
+ errors,
672
+ hosts)
662
673
  else
663
674
  prepare = prepare_statement(host, connection, cql, timeout)
664
675
  prepare.on_complete do |_|
@@ -816,10 +827,29 @@ module Cassandra
816
827
  cql = statement.cql
817
828
 
818
829
  if statement.is_a?(Statements::Bound)
819
- id = synchronize { @prepared_statements[host][cql] }
830
+ host_is_up = true
831
+ id = nil
832
+ synchronize do
833
+ if @prepared_statements[host].nil?
834
+ host_is_up = false
835
+ else
836
+ id = @prepared_statements[host][cql]
837
+ end
838
+ end
820
839
 
821
840
  if id
822
841
  request.add_prepared(id, statement.params, statement.params_types)
842
+ elsif !host_is_up
843
+ @logger.debug("#{host} is down; executing on next host in plan")
844
+ return batch_by_plan(promise,
845
+ keyspace,
846
+ batch_statement,
847
+ options,
848
+ request,
849
+ plan,
850
+ timeout,
851
+ errors,
852
+ hosts)
823
853
  else
824
854
  unprepared[cql] << statement
825
855
  end
@@ -1156,17 +1186,17 @@ module Cassandra
1156
1186
  end
1157
1187
  when Protocol::SetKeyspaceResultResponse
1158
1188
  @keyspace = r.keyspace
1159
- promise.fulfill(Results::Void.new(r.custom_payload,
1160
- r.warnings,
1161
- r.trace_id,
1162
- keyspace,
1163
- statement,
1164
- options,
1165
- hosts,
1166
- request.consistency,
1167
- retries,
1168
- self,
1169
- @futures))
1189
+ promise.fulfill(Cassandra::Results::Void.new(r.custom_payload,
1190
+ r.warnings,
1191
+ r.trace_id,
1192
+ keyspace,
1193
+ statement,
1194
+ options,
1195
+ hosts,
1196
+ request.consistency,
1197
+ retries,
1198
+ self,
1199
+ @futures))
1170
1200
  when Protocol::PreparedResultResponse
1171
1201
  cql = request.cql
1172
1202
  synchronize do
@@ -1328,7 +1358,7 @@ module Cassandra
1328
1358
  promise.fulfill(
1329
1359
  Results::Void.new(r.custom_payload,
1330
1360
  r.warnings,
1331
- r.trace_id,
1361
+ nil,
1332
1362
  keyspace,
1333
1363
  statement,
1334
1364
  options,
@@ -61,7 +61,7 @@ module Cassandra
61
61
  end
62
62
 
63
63
  def update(data)
64
- @name = data['name']
64
+ @name = data['cluster_name']
65
65
  @partitioner = @partitioners[data['partitioner']]
66
66
 
67
67
  self
@@ -25,7 +25,7 @@ module Cassandra
25
25
  attr_reader :auth_provider, :compressor, :connect_timeout, :credentials,
26
26
  :heartbeat_interval, :idle_timeout, :port, :schema_refresh_delay,
27
27
  :schema_refresh_timeout, :ssl
28
- attr_boolean :protocol_negotiable, :synchronize_schema, :client_timestamps, :nodelay
28
+ attr_boolean :protocol_negotiable, :synchronize_schema, :nodelay
29
29
 
30
30
  attr_accessor :protocol_version
31
31
 
@@ -44,7 +44,6 @@ module Cassandra
44
44
  synchronize_schema,
45
45
  schema_refresh_delay,
46
46
  schema_refresh_timeout,
47
- client_timestamps,
48
47
  nodelay,
49
48
  requests_per_connection)
50
49
  @logger = logger
@@ -60,7 +59,6 @@ module Cassandra
60
59
  @synchronize_schema = synchronize_schema
61
60
  @schema_refresh_delay = schema_refresh_delay
62
61
  @schema_refresh_timeout = schema_refresh_timeout
63
- @client_timestamps = client_timestamps
64
62
  @nodelay = nodelay
65
63
 
66
64
  @connections_per_local_node = connections_per_local_node
@@ -107,7 +107,8 @@ module Cassandra
107
107
  retry_policy,
108
108
  address_resolution_policy,
109
109
  connector,
110
- futures_factory)
110
+ futures_factory,
111
+ timestamp_generator)
111
112
  end
112
113
 
113
114
  let(:execution_options) do
@@ -135,7 +136,6 @@ module Cassandra
135
136
  synchronize_schema,
136
137
  schema_refresh_delay,
137
138
  schema_refresh_timeout,
138
- client_timestamps,
139
139
  nodelay,
140
140
  requests_per_connection
141
141
  )
@@ -165,15 +165,14 @@ module Cassandra
165
165
  let(:page_size) { 10000 }
166
166
  let(:heartbeat_interval) { 30 }
167
167
  let(:idle_timeout) { 60 }
168
- let(:timeout) { 10 }
168
+ let(:timeout) { 12 }
169
169
  let(:synchronize_schema) { true }
170
170
  let(:schema_refresh_delay) { 1 }
171
171
  let(:schema_refresh_timeout) { 10 }
172
172
  let(:thread_pool_size) { 4 }
173
173
  let(:shuffle_replicas) { true }
174
- let(:client_timestamps) { false }
175
174
  let(:nodelay) { true }
176
-
175
+ let(:timestamp_generator) { nil }
177
176
  let(:connections_per_local_node) { nil }
178
177
  let(:connections_per_remote_node) { nil }
179
178
  let(:requests_per_connection) { nil }
@@ -39,8 +39,17 @@ module Cassandra
39
39
  @table = table
40
40
  @name = name.freeze
41
41
  @kind = kind
42
- @target = target.freeze
43
42
  @options = options.freeze
43
+
44
+ # Target is a bit tricky; it may be an escaped name or not
45
+ # depending on C* version. Unify to be unescaped since a user
46
+ # who wants to know the target would want the bare column name.
47
+
48
+ @target = if target[0] == '"'
49
+ target[1..-2]
50
+ else
51
+ target
52
+ end.freeze
44
53
  end
45
54
 
46
55
  # @return [Boolean] whether or not this index uses a custom class.
@@ -57,13 +66,18 @@ module Cassandra
57
66
  def to_cql
58
67
  keyspace_name = Util.escape_name(@table.keyspace.name)
59
68
  table_name = Util.escape_name(@table.name)
60
- index_name = Util.escape_name(name)
69
+ index_name = Util.escape_name(@name)
70
+
71
+ # Target is interesting in that it's not necessarily a column name,
72
+ # so we can't simply escape it. If it contains a paren, we take it as is,
73
+ # otherwise assume it's a column name and escape accordingly.
74
+ escaped_target = @target.include?('(') ? @target : Util.escape_name(@target)
61
75
 
62
76
  if custom_index?
63
- "CREATE CUSTOM INDEX #{index_name} ON #{keyspace_name}.#{table_name} (#{target}) " \
64
- "USING '#{@options['class_name']}' #{options_cql};"
77
+ "CREATE CUSTOM INDEX #{index_name} ON #{keyspace_name}.#{table_name} (#{escaped_target}) " \
78
+ "USING '#{@options['class_name']}'#{options_cql};"
65
79
  else
66
- "CREATE INDEX #{index_name} ON #{keyspace_name}.#{table_name} (#{target});"
80
+ "CREATE INDEX #{index_name} ON #{keyspace_name}.#{table_name} (#{escaped_target});"
67
81
  end
68
82
  end
69
83
 
@@ -81,7 +95,7 @@ module Cassandra
81
95
  # @private
82
96
  def inspect
83
97
  "#<#{self.class.name}:0x#{object_id.to_s(16)} " \
84
- "@name=#{@name} table_name=#{@table.name} kind=#{@kind} target=#{@target}>"
98
+ "@name=#{@name.inspect} @table=#{@table.inspect} @kind=#{@kind.inspect} @target=#{@target.inspect}>"
85
99
  end
86
100
 
87
101
  private
@@ -93,9 +107,9 @@ module Cassandra
93
107
  end
94
108
  return '' if filtered_options.empty?
95
109
 
96
- result = 'WITH OPTIONS = {'
110
+ result = ' WITH OPTIONS = {'
97
111
  result << filtered_options.map do |key, value|
98
- "'#{key}':'#{value}'"
112
+ "'#{key}': '#{value}'"
99
113
  end.join(', ')
100
114
  result << '}'
101
115
  result
@@ -62,7 +62,7 @@ module Cassandra
62
62
  include MonitorMixin
63
63
 
64
64
  def initialize(datacenter = nil,
65
- max_remote_hosts_to_use = nil,
65
+ max_remote_hosts_to_use = 0,
66
66
  use_remote_hosts_for_local_consistency = false)
67
67
  datacenter &&= String(datacenter)
68
68
  max_remote_hosts_to_use &&= Integer(max_remote_hosts_to_use)
@@ -75,6 +75,13 @@ module Cassandra
75
75
  end
76
76
  end
77
77
 
78
+ # If use_remote* is true, max_remote* must be > 0
79
+ if use_remote_hosts_for_local_consistency
80
+ Util.assert(max_remote_hosts_to_use.nil? || max_remote_hosts_to_use > 0,
81
+ 'max_remote_hosts_to_use must be nil (meaning unlimited) or > 0 when ' \
82
+ 'use_remote_hosts_for_local_consistency is true')
83
+ end
84
+
78
85
  @datacenter = datacenter
79
86
  @max_remote = max_remote_hosts_to_use
80
87
  @local = ::Array.new
@@ -148,6 +155,17 @@ module Cassandra
148
155
 
149
156
  Plan.new(local, remote, position)
150
157
  end
158
+
159
+ # @private
160
+ def inspect
161
+ "#<#{self.class.name}:0x#{object_id.to_s(16)} " \
162
+ "datacenter=#{@datacenter.inspect}, " \
163
+ "use_remote=#{@use_remote.inspect}, " \
164
+ "max_remote=#{@max_remote.inspect}, " \
165
+ "local=#{@local.inspect}, " \
166
+ "remote=#{@remote.inspect}, " \
167
+ "position=#{@position.inspect}>"
168
+ end
151
169
  end
152
170
  end
153
171
  end
@@ -128,6 +128,13 @@ module Cassandra
128
128
 
129
129
  Plan.new(hosts, position)
130
130
  end
131
+
132
+ # @private
133
+ def inspect
134
+ "#<#{self.class.name}:0x#{object_id.to_s(16)} " \
135
+ "hosts=#{@hosts.inspect}, " \
136
+ "position=#{@position.inspect}>"
137
+ end
131
138
  end
132
139
  end
133
140
  end
@@ -136,6 +136,13 @@ module Cassandra
136
136
 
137
137
  Plan.new(replicas, @policy, keyspace, statement, options)
138
138
  end
139
+
140
+ # @private
141
+ def inspect
142
+ "#<#{self.class.name}:0x#{object_id.to_s(16)} " \
143
+ "policy=#{@policy.inspect}, " \
144
+ "shuffle=#{@shuffle.inspect}>"
145
+ end
139
146
  end
140
147
  end
141
148
  end
@@ -87,6 +87,13 @@ module Cassandra
87
87
  def host_down(host)
88
88
  @policy.host_down(host) if @ips.include?(host.ip)
89
89
  end
90
+
91
+ # @private
92
+ def inspect
93
+ "#<#{self.class.name}:0x#{object_id.to_s(16)} " \
94
+ "policy=#{@policy.inspect}, " \
95
+ "ips=#{@ips.inspect}>"
96
+ end
90
97
  end
91
98
  end
92
99
  end
@@ -324,10 +324,6 @@ module Cassandra
324
324
  self
325
325
  end
326
326
 
327
- def append_timestamp(timestamp)
328
- append_long(timestamp.tv_sec * 1000000 + timestamp.tv_usec)
329
- end
330
-
331
327
  def append_long(n)
332
328
  top = n >> 32
333
329
  bottom = n & 0xffffffff
@@ -81,7 +81,7 @@ module Cassandra
81
81
 
82
82
  buffer.append(flags.chr)
83
83
  buffer.append_consistency(@serial_consistency) if @serial_consistency
84
- buffer.append_timestamp(@timestamp) if @timestamp
84
+ buffer.append_long(@timestamp) if @timestamp
85
85
  end
86
86
 
87
87
  buffer
@@ -78,7 +78,7 @@ module Cassandra
78
78
  buffer.append_int(@page_size) if @page_size
79
79
  buffer.append_bytes(@paging_state) if @paging_state
80
80
  buffer.append_consistency(@serial_consistency) if @serial_consistency
81
- buffer.append_timestamp(@timestamp) if protocol_version > 2 && @timestamp
81
+ buffer.append_long(@timestamp) if protocol_version > 2 && @timestamp
82
82
  else
83
83
  encoder.write_parameters(buffer, @values, @metadata)
84
84
  buffer.append_consistency(@consistency)
@@ -71,7 +71,7 @@ module Cassandra
71
71
  buffer.append_int(@page_size) if @page_size
72
72
  buffer.append_bytes(@paging_state) if @paging_state
73
73
  buffer.append_consistency(@serial_consistency) if @serial_consistency
74
- buffer.append_timestamp(@timestamp) if protocol_version > 2 && @timestamp
74
+ buffer.append_long(@timestamp) if protocol_version > 2 && @timestamp
75
75
  end
76
76
  buffer
77
77
  end
@@ -238,7 +238,9 @@ module Cassandra
238
238
 
239
239
  # @private
240
240
  def inspect
241
- "#<#{self.class.name}:0x#{object_id.to_s(16)}>"
241
+ "#<#{self.class.name}:0x#{object_id.to_s(16)} " \
242
+ "@keyspace=#{keyspace.inspect}, " \
243
+ "@options=#{@options.inspect}>"
242
244
  end
243
245
  end
244
246
  end
@@ -80,14 +80,14 @@ module Cassandra
80
80
  else
81
81
  cql << ",\n"
82
82
  end
83
- cql << " #{column.name} #{type_to_cql(column.type, column.frozen?)}"
83
+ cql << " #{Util.escape_name(column.name)} #{type_to_cql(column.type, column.frozen?)}"
84
84
  cql << ' PRIMARY KEY' if primary_key && column.name == primary_key
85
85
  end
86
86
 
87
87
  unless primary_key
88
88
  cql << ",\n PRIMARY KEY ("
89
89
  if @partition_key.one?
90
- cql << @partition_key.first.name
90
+ cql << Util.escape_name(@partition_key.first.name)
91
91
  else
92
92
  cql << '('
93
93
  first = true
@@ -97,12 +97,12 @@ module Cassandra
97
97
  else
98
98
  cql << ', '
99
99
  end
100
- cql << column.name
100
+ cql << Util.escape_name(column.name)
101
101
  end
102
102
  cql << ')'
103
103
  end
104
104
  @clustering_columns.each do |column|
105
- cql << ", #{column.name}"
105
+ cql << ", #{Util.escape_name(column.name)}"
106
106
  end
107
107
  cql << ')'
108
108
  end
@@ -118,7 +118,7 @@ module Cassandra
118
118
  else
119
119
  cql << ', '
120
120
  end
121
- cql << "#{column.name} #{order.to_s.upcase}"
121
+ cql << "#{Util.escape_name(column.name)} #{order.to_s.upcase}"
122
122
  end
123
123
  cql << ")\n AND "
124
124
  end
@@ -0,0 +1,37 @@
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
+ module Cassandra
20
+ # A generator is used to create client-timestamps (in the form of long integers) to send with C* requests when
21
+ # the `:client_timestamps` cluster option is set to true.
22
+ #
23
+ # @abstract A timestamp generator given to {Cassandra.cluster} doesn't need to include this module, but needs to
24
+ # implement the same methods. This module exists only for documentation purposes.
25
+ module TimestampGenerator
26
+ # Create a new timestamp, as a 64-bit integer. Calls must return monotonically increasing values.
27
+ #
28
+ # @return [Integer] an integer representing a timestamp in microseconds.
29
+ # @raise [NotImplementedError] if a class including this module does not define this method.
30
+ def next
31
+ raise NotImplementedError, "#{self.class} class must implement the 'next' method"
32
+ end
33
+ end
34
+ end
35
+
36
+ require 'cassandra/timestamp_generator/ticking_on_duplicate'
37
+ require 'cassandra/timestamp_generator/simple'
@@ -0,0 +1,38 @@
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
+ module Cassandra
20
+ module TimestampGenerator
21
+ # Generate long integer timestamps from current time. This implementation relies on the {::Time} class to return
22
+ # microsecond precision time.
23
+ # @note It is not appropriate for use with JRuby because its {::Time#now} returns millisecond precision time.
24
+ class Simple
25
+ include TimestampGenerator
26
+
27
+ # Create a new timestamp, as a 64-bit integer. This is just a wrapper around Time::now.
28
+ #
29
+ # @return [Integer] an integer representing a timestamp in microseconds.
30
+ def next
31
+ # Use Time.now, which has microsecond precision on MRI (and probably Rubinius) to make an int representing
32
+ # client timestamp in protocol requests.
33
+ timestamp = ::Time.now
34
+ timestamp.tv_sec * 1000000 + timestamp.tv_usec
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,58 @@
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
+ module Cassandra
20
+ module TimestampGenerator
21
+ # In JRuby, {::Time} has millisecond precision. We require client timestamps to have microsecond precision to
22
+ # minimize clashes in C*. This generator keeps track of the last generated timestamp, and if the current-time
23
+ # is within the same millisecond as the last, it fills the microsecond portion of the new timestamp with the
24
+ # value of an incrementing counter.
25
+ #
26
+ # For example, if the generator triggers twice at time 12345678000 (microsecond granularity, but ms precisions
27
+ # as shown by 0's for the three least-significant digits), it'll return 12345678000 and 12345678001.
28
+ class TickingOnDuplicate
29
+ include MonitorMixin
30
+ include TimestampGenerator
31
+
32
+ # @private
33
+ def initialize
34
+ mon_initialize
35
+ @last = 0
36
+ end
37
+
38
+ # Create a new timestamp, as a 64-bit integer.
39
+ #
40
+ # @return [Integer] an integer representing a timestamp in microseconds.
41
+ def next
42
+ now = ::Time.now
43
+ now_millis = now.tv_sec * 1000 + now.tv_usec / 1000
44
+ synchronize do
45
+ millis = @last / 1000
46
+ counter = @last % 1000
47
+ if millis >= now_millis
48
+ counter += 1
49
+ else
50
+ millis = now_millis
51
+ counter = 0
52
+ end
53
+ @last = millis * 1000 + counter
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -162,8 +162,13 @@ module Cassandra
162
162
  end
163
163
 
164
164
  def escape_name(name)
165
- return name if name[LOWERCASE_REGEXP] == name
166
- DBL_QUOT + name + DBL_QUOT
165
+ # If name only contains lower-case chars and it's not a reserved word, return it
166
+ # as-is. Otherwise, quote.
167
+ return name if name[LOWERCASE_REGEXP] == name && !RESERVED_WORDS.include?(name)
168
+
169
+ # Replace double-quotes within name with two double-quotes (if any) and surround the whole
170
+ # thing with double-quotes
171
+ DBL_QUOT + name.gsub('"', '""') + DBL_QUOT
167
172
  end
168
173
 
169
174
  def guess_type(object)
@@ -327,5 +332,133 @@ module Cassandra
327
332
  PRN_OPN = '('.freeze
328
333
  # @private
329
334
  PRN_CLS = ')'.freeze
335
+ RESERVED_WORDS = Set.new(%w(
336
+ add
337
+ aggregate
338
+ all
339
+ allow
340
+ alter
341
+ and
342
+ apply
343
+ as
344
+ asc
345
+ ascii
346
+ authorize
347
+ batch
348
+ begin
349
+ bigint
350
+ blob
351
+ boolean
352
+ by
353
+ called
354
+ clustering
355
+ columnfamily
356
+ compact
357
+ contains
358
+ count
359
+ counter
360
+ create
361
+ custom
362
+ date
363
+ decimal
364
+ delete
365
+ desc
366
+ describe
367
+ distinct
368
+ double
369
+ drop
370
+ entries
371
+ execute
372
+ exists
373
+ filtering
374
+ finalfunc
375
+ float
376
+ from
377
+ frozen
378
+ full
379
+ function
380
+ functions
381
+ grant
382
+ if
383
+ in
384
+ index
385
+ inet
386
+ infinity
387
+ initcond
388
+ input
389
+ insert
390
+ int
391
+ into
392
+ is
393
+ json
394
+ key
395
+ keys
396
+ keyspace
397
+ keyspaces
398
+ language
399
+ limit
400
+ list
401
+ login
402
+ map
403
+ materialized
404
+ modify
405
+ nan
406
+ nologin
407
+ norecursive
408
+ nosuperuser
409
+ not
410
+ null
411
+ of
412
+ on
413
+ options
414
+ or
415
+ order
416
+ password
417
+ permission
418
+ permissions
419
+ primary
420
+ rename
421
+ replace
422
+ returns
423
+ revoke
424
+ role
425
+ roles
426
+ schema
427
+ select
428
+ set
429
+ sfunc
430
+ smallint
431
+ static
432
+ storage
433
+ stype
434
+ superuser
435
+ table
436
+ text
437
+ time
438
+ timestamp
439
+ timeuuid
440
+ tinyint
441
+ to
442
+ token
443
+ trigger
444
+ truncate
445
+ ttl
446
+ tuple
447
+ type
448
+ unlogged
449
+ update
450
+ use
451
+ user
452
+ users
453
+ using
454
+ uuid
455
+ values
456
+ varchar
457
+ varint
458
+ view
459
+ where
460
+ with
461
+ writetime
462
+ )).freeze
330
463
  end
331
464
  end
@@ -17,5 +17,5 @@
17
17
  #++
18
18
 
19
19
  module Cassandra
20
- VERSION = '3.0.0.rc.2'.freeze
20
+ VERSION = '3.0.0'.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.rc.2
4
+ version: 3.0.0
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-04-11 00:00:00.000000000 Z
13
+ date: 2016-05-23 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: ione
@@ -193,6 +193,9 @@ files:
193
193
  - lib/cassandra/table.rb
194
194
  - lib/cassandra/time.rb
195
195
  - lib/cassandra/time_uuid.rb
196
+ - lib/cassandra/timestamp_generator.rb
197
+ - lib/cassandra/timestamp_generator/simple.rb
198
+ - lib/cassandra/timestamp_generator/ticking_on_duplicate.rb
196
199
  - lib/cassandra/tuple.rb
197
200
  - lib/cassandra/types.rb
198
201
  - lib/cassandra/udt.rb
@@ -221,9 +224,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
221
224
  version: 1.9.3
222
225
  required_rubygems_version: !ruby/object:Gem::Requirement
223
226
  requirements:
224
- - - ! '>'
227
+ - - ! '>='
225
228
  - !ruby/object:Gem::Version
226
- version: 1.3.1
229
+ version: '0'
227
230
  requirements: []
228
231
  rubyforge_project:
229
232
  rubygems_version: 2.5.0