cassandra-driver 3.0.0.rc.2 → 3.0.0

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