From b1fa241761e9dadcaf804cf1425104200a68c566 Mon Sep 17 00:00:00 2001 From: Evan Prothro Date: Thu, 3 Mar 2016 14:26:49 -0600 Subject: [PATCH 001/196] move cluster options list into Cassandra::CLUSTER_OPTIONS --- lib/cassandra.rb | 82 +++++++++++++++++++++++++----------------------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/lib/cassandra.rb b/lib/cassandra.rb index a4ab7c641..5ff54d686 100644 --- a/lib/cassandra.rb +++ b/lib/cassandra.rb @@ -50,6 +50,47 @@ module Cassandra # @see https://github.com/apache/cassandra/blob/cassandra-2.0.16/doc/native_protocol_v2.spec#L872-L887 Description of possible types of writes in Apache Cassandra native protocol spec v1 WRITE_TYPES = [:simple, :batch, :unlogged_batch, :counter, :batch_log].freeze + CLUSTER_OPTIONS = [ + :address_resolution, + :address_resolution_policy, + :auth_provider, + :client_cert, + :client_timestamps, + :compression, + :compressor, + :connect_timeout, + :connections_per_local_node, + :connections_per_remote_node, + :consistency, + :credentials, + :datacenter, + :futures_factory, + :heartbeat_interval, + :hosts, + :idle_timeout, + :listeners, + :load_balancing_policy, + :logger, + :nodelay, + :reconnection_policy, + :retry_policy, + :page_size, + :passphrase, + :password, + :port, + :private_key, + :requests_per_connection, + :schema_refresh_delay, + :schema_refresh_timeout, + :server_cert, + :shuffle_replicas, + :ssl, + :synchronize_schema, + :timeout, + :trace, + :username + ].freeze + # Creates a {Cassandra::Cluster Cluster instance}. # # @option options [Array] :hosts (['127.0.0.1']) a list of @@ -277,46 +318,7 @@ def self.cluster_async(options = {}) # @private def self.validate_and_massage_options(options) options = options.select do |key, _| - [ - :address_resolution, - :address_resolution_policy, - :auth_provider, - :client_cert, - :client_timestamps, - :compression, - :compressor, - :connect_timeout, - :connections_per_local_node, - :connections_per_remote_node, - :consistency, - :credentials, - :datacenter, - :futures_factory, - :heartbeat_interval, - :hosts, - :idle_timeout, - :listeners, - :load_balancing_policy, - :logger, - :nodelay, - :reconnection_policy, - :retry_policy, - :page_size, - :passphrase, - :password, - :port, - :private_key, - :requests_per_connection, - :schema_refresh_delay, - :schema_refresh_timeout, - :server_cert, - :shuffle_replicas, - :ssl, - :synchronize_schema, - :timeout, - :trace, - :username - ].include?(key) + CLUSTER_OPTIONS.include?(key) end has_username = options.key?(:username) From 4c92d54e900ed134ec8488b81f52c159ddba44f3 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Fri, 4 Mar 2016 14:03:29 -0800 Subject: [PATCH 002/196] RUBY-176 - include version.rb to expose VERSION constant. --- lib/cassandra.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/cassandra.rb b/lib/cassandra.rb index 5ff54d686..b918df612 100644 --- a/lib/cassandra.rb +++ b/lib/cassandra.rb @@ -735,6 +735,7 @@ def self.validate_and_massage_options(options) DATE_OFFSET = (::Time.utc(1970, 1, 1).to_date.jd - 2**31) end +require 'cassandra/version' require 'cassandra/uuid' require 'cassandra/time_uuid' require 'cassandra/tuple' From d277b5e31988341aa75f3d31d9f8214170a2455c Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Thu, 10 Mar 2016 18:26:28 -0800 Subject: [PATCH 003/196] RUBY-165 - Add protocol_version config option. --- CHANGELOG.md | 1 + lib/cassandra.rb | 5 +++ lib/cassandra/cluster/connector.rb | 2 +- lib/cassandra/cluster/options.rb | 12 +++++++ lib/cassandra/driver.rb | 2 +- .../cluster/control_connection_spec.rb | 32 ++++++++++++++++++- 6 files changed, 51 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5a6549d2..ff602df3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # master Features: +* Add protocol_version configuration option to allow the user to force the protocol version to use for communication with nodes. Bug Fixes: * [RUBY-161] Protocol version negotiation in mixed version clusters should not fall back to v1 unless it is truly warranted. diff --git a/lib/cassandra.rb b/lib/cassandra.rb index b918df612..90346d373 100644 --- a/lib/cassandra.rb +++ b/lib/cassandra.rb @@ -79,6 +79,7 @@ module Cassandra :password, :port, :private_key, + :protocol_version, :requests_per_connection, :schema_refresh_delay, :schema_refresh_timeout, @@ -182,6 +183,10 @@ module Cassandra # for nodes that use the v3 or later protocol, and `128` for nodes that use the # v2 or earlier protocol. # + # @option options [Integer] :protocol_version (nil) Version of protocol to speak to + # nodes. By default, this is auto-negotiated to the lowest common protocol version + # that all nodes in `:hosts` speak. + # # @option options [Boolean] :client_timestamps (false) whether the driver # should send timestamps for each executed statement. Enabling this setting # allows mitigating Cassandra cluster clock skew because the timestamp of diff --git a/lib/cassandra/cluster/connector.rb b/lib/cassandra/cluster/connector.rb index 059c93f6c..da7ee7425 100644 --- a/lib/cassandra/cluster/connector.rb +++ b/lib/cassandra/cluster/connector.rb @@ -161,7 +161,7 @@ def do_connect(host) when Errors::ProtocolError synchronize do current_version = connection.protocol_version - if current_version > 1 + if current_version > 1 && @connection_options.protocol_negotiable? @logger.info("Host #{host.ip} doesn't support protocol version " \ "#{current_version}, downgrading") diff --git a/lib/cassandra/cluster/options.rb b/lib/cassandra/cluster/options.rb index e7493a173..3f4b422a0 100644 --- a/lib/cassandra/cluster/options.rb +++ b/lib/cassandra/cluster/options.rb @@ -63,6 +63,18 @@ def initialize(logger, @connections_per_local_node = connections_per_local_node @connections_per_remote_node = connections_per_remote_node @requests_per_connection = requests_per_connection + + # If @protocol_version is nil, it means we want the driver to negotiate the + # protocol starting with our known max (4). If @protocol_version is not nil, + # it means the user wants us to use a particular version, so we should not + # support negotiation. + + @protocol_negotiable = @protocol_version.nil? + @protocol_version ||= 4 + end + + def protocol_negotiable? + @protocol_negotiable end def synchronize_schema? diff --git a/lib/cassandra/driver.rb b/lib/cassandra/driver.rb index e3b6589e5..aea712883 100644 --- a/lib/cassandra/driver.rb +++ b/lib/cassandra/driver.rb @@ -142,7 +142,7 @@ def self.let(name, &block) end let(:port) { 9042 } - let(:protocol_version) { 4 } + let(:protocol_version) { nil } let(:connect_timeout) { 10 } let(:ssl) { false } let(:logger) { NullLogger.new } diff --git a/spec/cassandra/cluster/control_connection_spec.rb b/spec/cassandra/cluster/control_connection_spec.rb index 0c1ffe662..81d94aed1 100644 --- a/spec/cassandra/cluster/control_connection_spec.rb +++ b/spec/cassandra/cluster/control_connection_spec.rb @@ -44,7 +44,7 @@ class Cluster end let :driver do - Driver.new(protocol_version: 7, io_reactor: io_reactor) + Driver.new(protocol_version: nil, io_reactor: io_reactor) end let :load_balancing_policy do @@ -139,6 +139,7 @@ def handle_request(&handler) end before do + driver.connection_options.protocol_version = 7 cluster_registry.add_listener(driver.load_balancing_policy) cluster_registry.add_listener(control_connection) cluster_registry.host_found('127.0.0.1') @@ -251,6 +252,35 @@ def handle_request(&handler) counter.should == 7 end + it 'gives up when the protocol version is non-negotiable' do + + driver = Driver.new(protocol_version: 3, io_reactor: io_reactor) + io_reactor.connection_options = driver.connection_options + control_conn = ControlConnection.new(driver.logger, + driver.io_reactor, + driver.cluster_registry, + driver.cluster_schema, + driver.cluster_metadata, + driver.load_balancing_policy, + driver.reconnection_policy, + driver.address_resolution_policy, + driver.connector, + driver.connection_options, + driver.schema_fetcher) + + driver.cluster_registry.add_listener(driver.load_balancing_policy) + driver.cluster_registry.add_listener(control_conn) + driver.cluster_registry.host_found('127.0.0.1') + + counter = 0 + handle_request do |request| + counter += 1 + Protocol::ErrorResponse.new(nil, nil, 0x0a, 'Bork version, dummy!') + end + expect { control_conn.connect_async.value }.to raise_error(Cassandra::Errors::ProtocolError, 'Bork version, dummy!') + expect(counter).to eq(1) + end + it 'gives up when a non-protocol version related error is raised' do handle_request do |request| Protocol::ErrorResponse.new(nil, nil, 0x1001, 'Get off my lawn!') From 4695d2592f35e379e92fd6eb27902720a5f25ce7 Mon Sep 17 00:00:00 2001 From: Kishan Karunaratne Date: Fri, 11 Mar 2016 11:46:20 -0800 Subject: [PATCH 004/196] Update build.yaml to support Cassandra 3.4 --- build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.yaml b/build.yaml index 269713fd7..23d8a0654 100644 --- a/build.yaml +++ b/build.yaml @@ -7,7 +7,7 @@ cassandra: - 2.1 - 2.2 - 3.0 - - 3.3 + - 3.4 os: - ubuntu/trusty64 build: From 5818c07941d8a4709d426a3126102eb39f18cf39 Mon Sep 17 00:00:00 2001 From: Kishan Karunaratne Date: Fri, 11 Mar 2016 15:25:40 -0800 Subject: [PATCH 005/196] Fixed a bug with int/smallint mixup in uda test --- .../functions/user_defined_aggregate_test.rb | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/integration/functions/user_defined_aggregate_test.rb b/integration/functions/user_defined_aggregate_test.rb index a8d1bdcfa..1fe63ace5 100644 --- a/integration/functions/user_defined_aggregate_test.rb +++ b/integration/functions/user_defined_aggregate_test.rb @@ -43,19 +43,19 @@ def setup RETURNS map LANGUAGE java AS 'if (state.get(star_rating) == null) state.put(star_rating, 1); else state.put(star_rating, ((Integer) state.get(star_rating)) + 1); return state;'; - CREATE FUNCTION state_group_and_sum(state map, star_rating smallint) + CREATE FUNCTION state_group_and_sum(state map, star_rating smallint) CALLED ON NULL INPUT - RETURNS map + RETURNS map LANGUAGE java AS 'if (state.get(star_rating) == null) state.put(star_rating, 1); else state.put(star_rating, ((Integer) state.get(star_rating)) + 1); return state;'; CREATE FUNCTION percent_stars(state map) RETURNS NULL ON NULL INPUT RETURNS map LANGUAGE java AS 'Integer sum = 0; for(Object k : state.keySet()) { sum = sum + (Integer) state.get((Integer) k); } java.util.Map results = new java.util.HashMap(); for(Object k : state.keySet()) { results.put((Integer) k, ((Integer) state.get((Integer) k))*100 / sum); } return results;'; - CREATE FUNCTION percent_stars(state map) + CREATE FUNCTION percent_stars(state map) RETURNS NULL ON NULL INPUT - RETURNS map - LANGUAGE java AS 'Integer sum = 0; for(Object k : state.keySet()) { sum = sum + (Integer) state.get((Integer) k); } java.util.Map results = new java.util.HashMap(); for(Object k : state.keySet()) { results.put((Integer) k, ((Integer) state.get((Integer) k))*100 / sum); } return results;'; + RETURNS map + LANGUAGE java AS 'Integer sum = 0; for(Object k : state.keySet()) { sum = sum + (Integer) state.get((Short) k); } java.util.Map results = new java.util.HashMap(); for(Object k : state.keySet()) { results.put((Short) k, ((Integer) state.get((Short) k))*100 / sum); } return results;'; CREATE FUNCTION extend_list(s list, i int) CALLED ON NULL INPUT RETURNS list @@ -244,17 +244,17 @@ def test_udas_with_finalfunc # verify that we pick the right final func. @session.execute('CREATE OR REPLACE AGGREGATE group_and_sum2(smallint) SFUNC state_group_and_sum - STYPE map + STYPE map FINALFUNC percent_stars INITCOND NULL' ) @listener.wait_for_aggregate('simplex', 'group_and_sum2', smallint) aggregate = @cluster.keyspace('simplex').aggregate('group_and_sum2', smallint) - assert @cluster.keyspace('simplex').has_function?('state_group_and_sum', map(int, smallint), smallint) - state_function = @cluster.keyspace('simplex').function('state_group_and_sum', map(int, smallint), smallint) - assert @cluster.keyspace('simplex').has_function?('percent_stars', map(int, smallint)) - final_function = @cluster.keyspace('simplex').function('percent_stars', map(int, smallint)) + assert @cluster.keyspace('simplex').has_function?('state_group_and_sum', map(smallint, int), smallint) + state_function = @cluster.keyspace('simplex').function('state_group_and_sum', map(smallint, int), smallint) + assert @cluster.keyspace('simplex').has_function?('percent_stars', map(smallint, int)) + final_function = @cluster.keyspace('simplex').function('percent_stars', map(smallint, int)) assert_equal state_function, aggregate.state_function assert_equal final_function, aggregate.final_function From eb4e43f42148db8be701a4c06a32314dc1bec22b Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 14 Mar 2016 13:31:24 -0700 Subject: [PATCH 006/196] RUBY-168 - Expose broadcast_address and listen_address host attributes if available. --- CHANGELOG.md | 1 + lib/cassandra/cluster/control_connection.rb | 6 +- lib/cassandra/cluster/registry.rb | 21 +++- lib/cassandra/host.rb | 16 ++- spec/cassandra/cluster/registry_spec.rb | 107 +++++++++++++++++++- 5 files changed, 138 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff602df3f..04e235f50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # master Features: * Add protocol_version configuration option to allow the user to force the protocol version to use for communication with nodes. +* Expose listen_address and broadcast_address in `Cassandra::Host` if available. Bug Fixes: * [RUBY-161] Protocol version negotiation in mixed version clusters should not fall back to v1 unless it is truly warranted. diff --git a/lib/cassandra/cluster/control_connection.rb b/lib/cassandra/cluster/control_connection.rb index 34d82c3c6..55808e33b 100644 --- a/lib/cassandra/cluster/control_connection.rb +++ b/lib/cassandra/cluster/control_connection.rb @@ -147,20 +147,20 @@ def inspect private SELECT_LOCAL = Protocol::QueryRequest.new( - 'SELECT rack, data_center, host_id, release_version, tokens, partitioner ' \ + 'SELECT * ' \ 'FROM system.local', EMPTY_LIST, EMPTY_LIST, :one) SELECT_PEERS = Protocol::QueryRequest.new( - 'SELECT peer, rack, data_center, host_id, rpc_address, release_version, tokens ' \ + 'SELECT * ' \ 'FROM system.peers', EMPTY_LIST, EMPTY_LIST, :one) SELECT_PEER_QUERY = - 'SELECT rack, data_center, host_id, rpc_address, release_version, tokens ' \ + 'SELECT * ' \ 'FROM system.peers ' \ "WHERE peer = '%s'".freeze diff --git a/lib/cassandra/cluster/registry.rb b/lib/cassandra/cluster/registry.rb index c3e5217fb..1c6f15a7e 100644 --- a/lib/cassandra/cluster/registry.rb +++ b/lib/cassandra/cluster/registry.rb @@ -76,7 +76,9 @@ def host_found(address, data = {}) if host.id == data['host_id'] && host.release_version == data['release_version'] && host.rack == data['rack'] && - host.datacenter == data['data_center'] + host.datacenter == data['data_center'] && + host.broadcast_address == data['broadcast_address'] && + host.listen_address == data['listen_address'] return self if host.up? @@ -166,7 +168,10 @@ def create_host(ip, data) data['data_center'], data['release_version'], Array(data['tokens']).freeze, - :up) + :up, + data['broadcast_address'], + data['listen_address'] + ) end def toggle_up(host) @@ -176,7 +181,9 @@ def toggle_up(host) host.datacenter, host.release_version, host.tokens, - :up) + :up, + host.broadcast_address, + host.listen_address) @logger.debug("Host #{host.ip} is up") @listeners.each do |listener| begin @@ -195,7 +202,9 @@ def toggle_down(host) host.datacenter, host.release_version, host.tokens, - :down) + :down, + host.broadcast_address, + host.listen_address) @logger.debug("Host #{host.ip} is down") @listeners.reverse_each do |listener| begin @@ -216,7 +225,9 @@ def notify_lost(host) host.datacenter, host.release_version, host.tokens, - :down) + :down, + host.broadcast_address, + host.listen_address) @listeners.reverse_each do |listener| begin listener.host_down(host) diff --git a/lib/cassandra/host.rb b/lib/cassandra/host.rb index daaecaebf..0dc8a5bfe 100644 --- a/lib/cassandra/host.rb +++ b/lib/cassandra/host.rb @@ -18,7 +18,7 @@ module Cassandra class Host - # @return [IPAddr] host ip + # @return [IPAddr] host ip that clients use to connect to this host. attr_reader :ip # @note Host id can be `nil` before cluster has connected. # @return [Cassandra::Uuid, nil] host id. @@ -37,6 +37,12 @@ class Host attr_reader :tokens # @return [Symbol] host status. Must be `:up` or `:down` attr_reader :status + # @note This is the public IP address of the host if the cluster is deployed across multiple Amazon EC2 regions (or equivalently multiple networks). Cassandra nodes in other EC2 regions use this address to connect to this host. + # @return [IPAddr, String] broadcast address, if available. + attr_reader :broadcast_address + # @note This is the address that other Cassandra nodes use to connect to this host. + # @return [IPAddr, String] listen address, if available. + attr_reader :listen_address # @private def initialize(ip, @@ -45,7 +51,9 @@ def initialize(ip, datacenter = nil, release_version = nil, tokens = EMPTY_LIST, - status = :up) + status = :up, + broadcast_address = nil, + listen_address = nil) @ip = ip @id = id @rack = rack @@ -53,6 +61,10 @@ def initialize(ip, @release_version = release_version @tokens = tokens @status = status + @broadcast_address = broadcast_address.is_a?(String) ? + ::IPAddr.new(broadcast_address) : broadcast_address + @listen_address = listen_address.is_a?(String) ? + ::IPAddr.new(listen_address) : listen_address end # @return [Boolean] whether this host's status is `:up` diff --git a/spec/cassandra/cluster/registry_spec.rb b/spec/cassandra/cluster/registry_spec.rb index 3599fc995..5e6b847b1 100644 --- a/spec/cassandra/cluster/registry_spec.rb +++ b/spec/cassandra/cluster/registry_spec.rb @@ -34,19 +34,120 @@ class Cluster context('when host is unknown') do it 'notifies listeners' do + expect(listener).to receive(:host_found).twice do |host| + expect(host.tokens).to eq([]) + expect(host).to be_up + + if host.ip == ::IPAddr.new('127.0.0.1') + expect(host.broadcast_address).to be_nil + expect(host.listen_address).to be_nil + else + expect(host.broadcast_address).to eq(::IPAddr.new('127.0.0.127')) + expect(host.listen_address).to eq(::IPAddr.new('127.0.0.128')) + end + end + + expect(listener).to receive(:host_up).twice do |host| + expect(host.tokens).to eq([]) + expect(host).to be_up + + if host.ip == ::IPAddr.new('127.0.0.1') + expect(host.broadcast_address).to be_nil + expect(host.listen_address).to be_nil + else + expect(host.broadcast_address).to eq(::IPAddr.new('127.0.0.127')) + expect(host.listen_address).to eq(::IPAddr.new('127.0.0.128')) + end + end + + subject.host_found(::IPAddr.new('127.0.0.1'), {}) + subject.host_found(::IPAddr.new('127.0.0.2'), { + 'broadcast_address' => '127.0.0.127', + 'listen_address' => '127.0.0.128' + }) + end + end + + context('when host is known') do + before do + allow(listener).to receive(:host_found) + allow(listener).to receive(:host_up) + subject.host_found(::IPAddr.new('127.0.0.2'), { + 'broadcast_address' => '127.0.0.127', + 'listen_address' => '127.0.0.128' + }) + end + let (:host) { + subject.host('127.0.0.2') + } + + it 'notifies listeners when doing down to up' do + # First bring down the host; we want to test that bringing it up doesn't lose + # info. + + host.instance_variable_set(:@status, :down) + + # Now bring it up and verify that it still has broadcast_address and + # listen_address. + expect(listener).to receive(:host_up).once do |host| + expect(host.tokens).to eq([]) + expect(host).to be_up + + expect(host.broadcast_address).to eq(::IPAddr.new('127.0.0.127')) + expect(host.listen_address).to eq(::IPAddr.new('127.0.0.128')) + end + + subject.host_found(::IPAddr.new('127.0.0.2'), { + 'broadcast_address' => '127.0.0.127', + 'listen_address' => '127.0.0.128' + }) + end + + it 'notifies listeners when doing up to down' do + expect(listener).to receive(:host_down).once do |host| + expect(host.tokens).to eq([]) + expect(host).to be_down + + expect(host.broadcast_address).to eq(::IPAddr.new('127.0.0.127')) + expect(host.listen_address).to eq(::IPAddr.new('127.0.0.128')) + end + + subject.host_down(::IPAddr.new('127.0.0.2')) + end + + it 're-creates host when metadata has changed' do + expect(listener).to receive(:host_down).once do |host| + expect(host.tokens).to eq([]) + expect(host).to be_down + expect(host.broadcast_address).to eq(::IPAddr.new('127.0.0.127')) + expect(host.listen_address).to eq(::IPAddr.new('127.0.0.128')) + end + + expect(listener).to receive(:host_lost).once do |host| + expect(host.tokens).to eq([]) + expect(host).to be_down + expect(host.broadcast_address).to eq(::IPAddr.new('127.0.0.127')) + expect(host.listen_address).to eq(::IPAddr.new('127.0.0.128')) + end + expect(listener).to receive(:host_found).once do |host| - expect(host.ip).to eq(::IPAddr.new('127.0.0.1')) expect(host.tokens).to eq([]) expect(host).to be_up + expect(host.broadcast_address).to eq(::IPAddr.new('127.0.0.227')) + expect(host.listen_address).to eq(::IPAddr.new('127.0.0.228')) end expect(listener).to receive(:host_up).once do |host| - expect(host.ip).to eq(::IPAddr.new('127.0.0.1')) expect(host.tokens).to eq([]) expect(host).to be_up + expect(host.broadcast_address).to eq(::IPAddr.new('127.0.0.227')) + expect(host.listen_address).to eq(::IPAddr.new('127.0.0.228')) end - subject.host_found(::IPAddr.new('127.0.0.1'), {}) + subject.host_found(::IPAddr.new('127.0.0.2'), { + 'broadcast_address' => '127.0.0.227', + 'listen_address' => '127.0.0.228' + }) end end end From 0aeb9b98bbb65829fb52c857b64546307b82a4b4 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Tue, 15 Mar 2016 13:02:57 -0700 Subject: [PATCH 007/196] RUBY-169 - Ignore authenticator name when creating password authenticator. NB: Not updating CHANGELOG because this is so under the hood that I don't think users would notice. --- lib/cassandra/auth/providers/password.rb | 16 +++++----------- spec/cassandra/auth/providers/password_spec.rb | 2 +- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/lib/cassandra/auth/providers/password.rb b/lib/cassandra/auth/providers/password.rb index 0f2e91fa0..b5f088224 100644 --- a/lib/cassandra/auth/providers/password.rb +++ b/lib/cassandra/auth/providers/password.rb @@ -53,17 +53,11 @@ def initialize(username, password) @password = password end - # Returns a Password Authenticator only if - # `org.apache.cassandra.auth.PasswordAuthenticator` is given. - # @param authentication_class [String] must equal to - # `org.apache.cassandra.auth.PasswordAuthenticator` - # @return [Cassandra::Auth::Authenticator] when `authentication_class == - # "org.apache.cassandra.auth.PasswordAuthenticator"` - # @return [nil] for all other values of `authentication_class` - def create_authenticator(authentication_class) - if authentication_class == PASSWORD_AUTHENTICATOR_FQCN - Authenticator.new(@username, @password) - end + # Returns a Password Authenticator + # @param authentication_class [String] ignored and deprecated + # @return [Cassandra::Auth::Authenticator] + def create_authenticator(authentication_class = nil) + Authenticator.new(@username, @password) end private diff --git a/spec/cassandra/auth/providers/password_spec.rb b/spec/cassandra/auth/providers/password_spec.rb index 41bdeb888..52548c8a9 100644 --- a/spec/cassandra/auth/providers/password_spec.rb +++ b/spec/cassandra/auth/providers/password_spec.rb @@ -39,7 +39,7 @@ module Providers it 'returns nil when the authentication class is not org.apache.cassandra.auth.PasswordAuthenticator' do authenticator = auth_provider.create_authenticator('org.acme.Foo') - authenticator.should be_nil + authenticator.initial_response.should == "\x00foo\x00bar" end end end From 4689010c4e90e16e260fc3b6e3626a5ae62afe93 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Tue, 15 Mar 2016 16:37:05 -0700 Subject: [PATCH 008/196] RUBY-169 - Ignore authenticator name when creating password authenticator. * Made authentication_class arg of create_authenticator required again. --- lib/cassandra/auth/providers/password.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/cassandra/auth/providers/password.rb b/lib/cassandra/auth/providers/password.rb index b5f088224..1128d4e10 100644 --- a/lib/cassandra/auth/providers/password.rb +++ b/lib/cassandra/auth/providers/password.rb @@ -54,9 +54,9 @@ def initialize(username, password) end # Returns a Password Authenticator - # @param authentication_class [String] ignored and deprecated + # @param authentication_class [String] ignored # @return [Cassandra::Auth::Authenticator] - def create_authenticator(authentication_class = nil) + def create_authenticator(authentication_class) Authenticator.new(@username, @password) end From b7075b768bb266adb42efc238e61e9cee2203fb8 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Fri, 18 Mar 2016 23:31:02 -0700 Subject: [PATCH 009/196] RUBY-167 - Add materialized view support to the Ruby driver. RUBY-179 - Added crc_check_chance table/view options attribute. RUBY-180 - Column ordering is not deterministic in Table. --- CHANGELOG.md | 2 + README.md | 3 + lib/cassandra.rb | 2 + lib/cassandra/cluster/control_connection.rb | 52 ++-- lib/cassandra/cluster/schema.rb | 44 ++++ lib/cassandra/cluster/schema/fetchers.rb | 267 ++++++++++++------- lib/cassandra/column_container.rb | 257 +++++++++++++++++++ lib/cassandra/keyspace.rb | 89 ++++++- lib/cassandra/materialized_view.rb | 125 +++++++++ lib/cassandra/table.rb | 269 ++++---------------- spec/cassandra/cluster/metadata_spec.rb | 2 +- 11 files changed, 777 insertions(+), 335 deletions(-) create mode 100644 lib/cassandra/column_container.rb create mode 100644 lib/cassandra/materialized_view.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index ff602df3f..5bb7aec93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # master Features: * Add protocol_version configuration option to allow the user to force the protocol version to use for communication with nodes. +* Add support for materialized views in the schema metadata. +* Add the crc_check_chance attribute to table and view options. Bug Fixes: * [RUBY-161] Protocol version negotiation in mixed version clusters should not fall back to v1 unless it is truly warranted. diff --git a/README.md b/README.md index 25d0a4928..3f79e919a 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,8 @@ Some of the new features added to the driver have unfortunately led to changes i * Expose server warnings on server exceptions and Cassandra::Execution::Info instances. * Add connections_per_local_node, connections_per_remote_node, requests_per_connection cluster configuration options to tune parallel query execution and resource usage. * Add Cassandra::Logger class to make it easy for users to enable debug logging in the client. +* Add protocol_version configuration option to allow the user to force the protocol version to use for communication with nodes. +* Add support for materialized views in the schema metadata. ### Breaking Changes: @@ -133,6 +135,7 @@ batch.add(query, arguments: {p1: 'val1'}) * [[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. * [[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. * [[RUBY-151](https://datastax-oss.atlassian.net/browse/RUBY-151)] Decode incomplete UDTs properly. +* [[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. ## Feedback Requested diff --git a/lib/cassandra.rb b/lib/cassandra.rb index 90346d373..45714dbb8 100644 --- a/lib/cassandra.rb +++ b/lib/cassandra.rb @@ -771,7 +771,9 @@ def self.validate_and_massage_options(options) require 'cassandra/function' require 'cassandra/function_collection' require 'cassandra/column' +require 'cassandra/column_container' require 'cassandra/table' +require 'cassandra/materialized_view' require 'cassandra/keyspace' require 'cassandra/execution/info' diff --git a/lib/cassandra/cluster/control_connection.rb b/lib/cassandra/cluster/control_connection.rb index 34d82c3c6..c88029847 100644 --- a/lib/cassandra/cluster/control_connection.rb +++ b/lib/cassandra/cluster/control_connection.rb @@ -284,7 +284,7 @@ def refresh_keyspace_async(keyspace_name) @schema.delete_keyspace(keyspace_name) end - @logger.info("Refreshed keyspace \"#{keyspace_name}\"") + @logger.info("Completed refreshing keyspace \"#{keyspace_name}\"") end end @@ -304,7 +304,27 @@ def refresh_table_async(keyspace_name, table_name) @schema.delete_table(keyspace_name, table_name) end - @logger.info("Refreshed table \"#{keyspace_name}.#{table_name}\"") + @logger.info("Completed refreshing table \"#{keyspace_name}.#{table_name}\"") + end + end + + def refresh_materialized_view_async(keyspace_name, view_name) + connection = @connection + + if connection.nil? + return Ione::Future.failed(Errors::ClientError.new('Not connected')) + end + + @logger.info("Refreshing materialized view \"#{keyspace_name}.#{view_name}\"") + + @schema_fetcher.fetch_materialized_view(connection, keyspace_name, view_name).map do |view| + if view + @schema.replace_materialized_view(view) + else + @schema.delete_materialized_view(keyspace_name, view_name) + end + + @logger.info("Completed refreshing materialized view \"#{keyspace_name}.#{view_name}\"") end end @@ -324,7 +344,7 @@ def refresh_type_async(keyspace_name, type_name) @schema.delete_type(keyspace_name, type_name) end - @logger.info("Refreshed user-defined type \"#{keyspace_name}.#{type_name}\"") + @logger.info("Completed refreshing user-defined type \"#{keyspace_name}.#{type_name}\"") end end @@ -351,7 +371,7 @@ def refresh_function_async(keyspace_name, function_name, function_args) @schema.delete_function(keyspace_name, function_name, parsed_function_args) end - @logger.info('Refreshed user-defined function ' \ + @logger.info('Completed refreshing user-defined function ' \ "\"#{keyspace_name}.#{function_name}(#{function_args.join(',')})\"") end end @@ -380,7 +400,7 @@ def refresh_aggregate_async(keyspace_name, aggregate_name, aggregate_args) @schema.delete_aggregate(keyspace_name, aggregate_name, parsed_aggregate_args) end - @logger.info('Refreshed user-defined aggregate ' \ + @logger.info('Completed refreshing user-defined aggregate ' \ "\"#{keyspace_name}.#{aggregate_name}(#{aggregate_args.join(',')})\"") end end @@ -456,7 +476,7 @@ def refresh_peers_async @registry.host_lost(host.ip) unless ips.include?(host.ip) end - @logger.info('Refreshed peers metadata') + @logger.info('Completed refreshing peers metadata') nil end @@ -480,7 +500,7 @@ def refresh_metadata_async @metadata.update(data) end - @logger.info("Refreshed connected host's metadata") + @logger.info("Completed refreshing connected host's metadata") nil end @@ -580,7 +600,7 @@ def refresh_host_async(address) if rows.empty? raise Errors::InternalError, "Unable to find host metadata: #{ip}" else - @logger.info("Refreshed host metadata: #{ip}") + @logger.info("Completed refreshing host metadata: #{ip}") @registry.host_found(address, rows.first) end @@ -684,7 +704,7 @@ def peer_ip(data) def process_schema_changes(schema_changes) refresh_keyspaces = ::Hash.new - refresh_tables = ::Hash.new + refresh_tables_and_views = ::Hash.new refresh_types = ::Hash.new # This hash is of the form @@ -700,14 +720,15 @@ def process_schema_changes(schema_changes) case change.target when Protocol::Constants::SCHEMA_CHANGE_TARGET_KEYSPACE - refresh_tables.delete(keyspace) + refresh_tables_and_views.delete(keyspace) refresh_types.delete(keyspace) refresh_functions.delete(keyspace) refresh_aggregates.delete(keyspace) refresh_keyspaces[keyspace] = true when Protocol::Constants::SCHEMA_CHANGE_TARGET_TABLE - tables = refresh_tables[keyspace] ||= ::Hash.new - tables[change.name] = true + # We can't distinguish between table and view change events, so refresh both. + tables_and_views = refresh_tables_and_views[keyspace] ||= ::Hash.new + tables_and_views[change.name] = true when Protocol::Constants::SCHEMA_CHANGE_TARGET_UDT types = refresh_types[keyspace] ||= ::Hash.new types[change.name] = true @@ -726,9 +747,10 @@ def process_schema_changes(schema_changes) futures << refresh_maybe_retry(:keyspace, keyspace) end - refresh_tables.each do |(keyspace, tables)| - tables.each_key do |table| - futures << refresh_maybe_retry(:table, keyspace, table) + refresh_tables_and_views.each do |(keyspace, tables_and_views)| + tables_and_views.each_key do |table_or_view| + futures << refresh_maybe_retry(:table, keyspace, table_or_view) + futures << refresh_maybe_retry(:materialized_view, keyspace, table_or_view) end end diff --git a/lib/cassandra/cluster/schema.rb b/lib/cassandra/cluster/schema.rb index 961fe90e5..e266267ba 100644 --- a/lib/cassandra/cluster/schema.rb +++ b/lib/cassandra/cluster/schema.rb @@ -169,6 +169,50 @@ def delete_table(keyspace_name, table_name) self end + def replace_materialized_view(view) + keyspace = @keyspaces[view.keyspace] + + return self unless keyspace + + old_view = keyspace.materialized_view(view.name) + + return self if old_view == view + + keyspace = keyspace.update_materialized_view(view) + + synchronize do + keyspaces = @keyspaces.dup + keyspaces[keyspace.name] = keyspace + @keyspaces = keyspaces + end + + keyspace_changed(keyspace) + + self + end + + def delete_materialized_view(keyspace_name, view_name) + keyspace = @keyspaces[keyspace_name] + + return self unless keyspace + + view = keyspace.materialized_view(view_name) + + return self unless view + + keyspace = keyspace.delete_materialized_view(view_name) + + synchronize do + keyspaces = @keyspaces.dup + keyspaces[keyspace_name] = keyspace + @keyspaces = keyspaces + end + + keyspace_changed(keyspace) + + self + end + def replace_type(type) keyspace = @keyspaces[type.keyspace] diff --git a/lib/cassandra/cluster/schema/fetchers.rb b/lib/cassandra/cluster/schema/fetchers.rb index e4d115cda..51f696670 100644 --- a/lib/cassandra/cluster/schema/fetchers.rb +++ b/lib/cassandra/cluster/schema/fetchers.rb @@ -31,15 +31,18 @@ def fetch(connection) select_columns(connection), select_types(connection), select_functions(connection), - select_aggregates(connection)) + select_aggregates(connection), + select_materialized_views(connection)) .map do |(rows_keyspaces, rows_tables, rows_columns, - rows_types, rows_functions, rows_aggregates)| + rows_types, rows_functions, rows_aggregates, + rows_views)| lookup_tables = map_rows_by(rows_tables, 'keyspace_name') lookup_columns = map_rows_by(rows_columns, 'keyspace_name') lookup_types = map_rows_by(rows_types, 'keyspace_name') lookup_functions = map_rows_by(rows_functions, 'keyspace_name') lookup_aggregates = map_rows_by(rows_aggregates, 'keyspace_name') + lookup_views = map_rows_by(rows_views, 'keyspace_name') rows_keyspaces.map do |keyspace_data| name = keyspace_data['keyspace_name'] @@ -49,7 +52,8 @@ def fetch(connection) lookup_columns[name], lookup_types[name], lookup_functions[name], - lookup_aggregates[name]) + lookup_aggregates[name], + lookup_views[name]) end end end @@ -60,9 +64,11 @@ def fetch_keyspace(connection, keyspace_name) select_keyspace_columns(connection, keyspace_name), select_keyspace_types(connection, keyspace_name), select_keyspace_functions(connection, keyspace_name), - select_keyspace_aggregates(connection, keyspace_name)) + select_keyspace_aggregates(connection, keyspace_name), + select_keyspace_materialized_views(connection, keyspace_name)) .map do |(rows_keyspaces, rows_tables, rows_columns, - rows_types, rows_functions, rows_aggregates)| + rows_types, rows_functions, rows_aggregates, + rows_views)| if rows_keyspaces.empty? nil else @@ -71,7 +77,8 @@ def fetch_keyspace(connection, keyspace_name) rows_columns, rows_types, rows_functions, - rows_aggregates) + rows_aggregates, + rows_views) end end end @@ -89,6 +96,22 @@ def fetch_table(connection, keyspace_name, table_name) end end + def fetch_materialized_view(connection, keyspace_name, view_name) + Ione::Future.all(select_materialized_view(connection, keyspace_name, view_name), + select_table_columns(connection, keyspace_name, view_name)) + .map do |(rows_views, rows_columns)| + if rows_views.empty? + nil + else + view_row = rows_views.first + base_table = @schema.keyspace(keyspace_name).table(view_row['base_table_name']) + create_materialized_view(view_row, + rows_columns, + base_table) + end + end + end + def fetch_type(connection, keyspace_name, type_name) select_type(connection, keyspace_name, type_name).map do |rows_types| if rows_types.empty? @@ -132,6 +155,10 @@ def select_tables(connection) FUTURE_EMPTY_LIST end + def select_materialized_views(connection) + FUTURE_EMPTY_LIST + end + def select_columns(connection) FUTURE_EMPTY_LIST end @@ -156,6 +183,10 @@ def select_keyspace_tables(connection, keyspace_name) FUTURE_EMPTY_LIST end + def select_keyspace_materialized_views(connection, keyspace_name) + FUTURE_EMPTY_LIST + end + def select_keyspace_columns(connection, keyspace_name) FUTURE_EMPTY_LIST end @@ -176,6 +207,10 @@ def select_table(connection, keyspace_name, table_name) FUTURE_EMPTY_LIST end + def select_materialized_view(connection, keyspace_name, view_name) + FUTURE_EMPTY_LIST + end + def select_table_columns(connection, keyspace_name, table_name) FUTURE_EMPTY_LIST end @@ -248,7 +283,7 @@ class V1_2_x 'FROM system.schema_columns ' \ 'WHERE keyspace_name = \'%s\' AND columnfamily_name = \'%s\''.freeze - include Fetcher + include Cassandra::Cluster::Schema::Fetcher def initialize(type_parser, schema) @type_parser = type_parser @@ -298,7 +333,8 @@ def create_replication(keyspace_data) end def create_keyspace(keyspace_data, rows_tables, rows_columns, - rows_types, rows_functions, rows_aggregates) + rows_types, rows_functions, rows_aggregates, + rows_views) keyspace_name = keyspace_data['keyspace_name'] replication = create_replication(keyspace_data) types = rows_types.each_with_object({}) do |row, types| @@ -328,7 +364,8 @@ def create_keyspace(keyspace_data, rows_tables, rows_columns, tables, types, functions, - aggregates) + aggregates, + {}) end def create_table(table_data, rows_columns) @@ -338,7 +375,16 @@ def create_table(table_data, rows_columns) comparator = @type_parser.parse(table_data['comparator']) column_aliases = ::JSON.load(table_data['column_aliases']) - if !comparator.collections.nil? + if comparator.collections.nil? + is_compact = true + if !column_aliases.empty? || rows_columns.empty? + has_value = true + clustering_size = comparator.results.size + else + has_value = false + clustering_size = 0 + end + else size = comparator.results.size if !comparator.collections.empty? is_compact = false @@ -354,26 +400,17 @@ def create_table(table_data, rows_columns) has_value = (!column_aliases.empty? || rows_columns.empty?) clustering_size = size end - else - is_compact = true - if (!column_aliases.empty? || rows_columns.empty?) - has_value = true - clustering_size = comparator.results.size - else - has_value = false - clustering_size = 0 - end end + # Separate out the partition-key, clustering-columns, and other-columns partition_key = [] clustering_columns = [] clustering_order = [] + other_columns = [] compaction_strategy = create_compaction_strategy(table_data) table_options = create_table_options(table_data, compaction_strategy, is_compact) - table_columns = {} - other_columns = [] key_aliases = ::JSON.load(table_data['key_aliases']) @@ -408,23 +445,11 @@ def create_table(table_data, rows_columns) other_columns << create_column(row) end - partition_key.each do |column| - table_columns[column.name] = column - end - - clustering_columns.each do |column| - table_columns[column.name] = column - end - - other_columns.each do |column| - table_columns[column.name] = column - end - - Table.new(keyspace_name, + Cassandra::Table.new(keyspace_name, table_name, partition_key, clustering_columns, - table_columns, + other_columns, table_options, clustering_order) end @@ -453,7 +478,7 @@ def create_compaction_strategy(table_data) klass = table_data['compaction_strategy_class'] klass.slice!('org.apache.cassandra.db.compaction.') options = ::JSON.load(table_data['compaction_strategy_options']) - Table::Compaction.new(klass, options) + ColumnContainer::Compaction.new(klass, options) end def create_table_options(table_data, compaction_strategy, is_compact) @@ -462,7 +487,7 @@ def create_table_options(table_data, compaction_strategy, is_compact) compression_parameters['sstable_compression']. slice!(COMPRESSION_PACKAGE_PREFIX) end - Table::Options.new( + Cassandra::ColumnContainer::Options.new( table_data['comment'], table_data['read_repair_chance'], table_data['local_read_repair_chance'], @@ -479,7 +504,8 @@ def create_table_options(table_data, compaction_strategy, is_compact) nil, compaction_strategy, compression_parameters, - is_compact + is_compact, + table_data['crc_check_chance'] ) end end @@ -506,13 +532,13 @@ def create_table(table_data, rows_columns) keyspace_name = table_data['keyspace_name'] table_name = table_data['columnfamily_name'] comparator = @type_parser.parse(table_data['comparator']) - table_columns = {} - other_columns = [] clustering_size = 0 + # Separate out partition-key, clustering columns, other columns. partition_key = [] clustering_columns = [] clustering_order = [] + other_columns = [] rows_columns.each do |row| next if row['column_name'].empty? @@ -536,29 +562,17 @@ def create_table(table_data, rows_columns) end end - partition_key.each do |column| - table_columns[column.name] = column - end - - clustering_columns.each do |column| - table_columns[column.name] = column - end - - other_columns.each do |column| - table_columns[column.name] = column - end - compaction_strategy = create_compaction_strategy(table_data) is_compact = (clustering_size != comparator.results.size - 1) || !comparator.collections table_options = create_table_options(table_data, compaction_strategy, is_compact) - Table.new(keyspace_name, + Cassandra::Table.new(keyspace_name, table_name, partition_key, clustering_columns, - table_columns, + other_columns, table_options, clustering_order) end @@ -599,7 +613,7 @@ def create_table_options(table_data, compaction_strategy, is_compact) compression_parameters['sstable_compression']. slice!(COMPRESSION_PACKAGE_PREFIX) end - Table::Options.new( + Cassandra::ColumnContainer::Options.new( table_data['comment'], table_data['read_repair_chance'], table_data['local_read_repair_chance'], @@ -616,7 +630,8 @@ def create_table_options(table_data, compaction_strategy, is_compact) nil, compaction_strategy, compression_parameters, - is_compact + is_compact, + table_data['crc_check_chance'], ) end end @@ -671,7 +686,7 @@ def create_table_options(table_data, compaction_strategy, is_compact) compression_parameters['sstable_compression']. slice!(COMPRESSION_PACKAGE_PREFIX) end - Table::Options.new( + Cassandra::ColumnContainer::Options.new( table_data['comment'], table_data['read_repair_chance'], table_data['local_read_repair_chance'], @@ -688,7 +703,8 @@ def create_table_options(table_data, compaction_strategy, is_compact) table_data['max_index_interval'], compaction_strategy, compression_parameters, - is_compact + is_compact, + table_data['crc_check_chance'] ) end end @@ -742,7 +758,7 @@ def create_function(function_data) arguments << Argument.new(argument_name, argument_type) end - Function.new(keyspace_name, + Cassandra::Function.new(keyspace_name, function_name, function_lang, function_type, @@ -825,7 +841,7 @@ class V3_0_x < V2_2_x SELECT_FUNCTIONS = "SELECT * FROM system_schema.functions".freeze SELECT_AGGREGATES = "SELECT * FROM system_schema.aggregates".freeze SELECT_INDEXES = "SELECT * FROM system_schema.indexes".freeze - SELECT_VIEWS = "SELECT * FROM system_schema.materialized_views".freeze + SELECT_VIEWS = "SELECT * FROM system_schema.views".freeze SELECT_KEYSPACE = 'SELECT * FROM system_schema.keyspaces WHERE keyspace_name = ?'.freeze @@ -833,6 +849,8 @@ class V3_0_x < V2_2_x 'SELECT * FROM system_schema.tables WHERE keyspace_name = ?'.freeze SELECT_KEYSPACE_COLUMNS = 'SELECT * FROM system_schema.columns WHERE keyspace_name = ?'.freeze + SELECT_KEYSPACE_VIEWS = + 'SELECT * FROM system_schema.views WHERE keyspace_name = ?'.freeze SELECT_KEYSPACE_TYPES = "SELECT * FROM system_schema.types WHERE keyspace_name = ?".freeze SELECT_KEYSPACE_FUNCTIONS = @@ -849,6 +867,11 @@ class V3_0_x < V2_2_x 'FROM system_schema.columns ' \ 'WHERE keyspace_name = ? AND table_name = ?'.freeze + SELECT_VIEW = + 'SELECT * ' \ + 'FROM system_schema.views ' \ + 'WHERE keyspace_name = ? AND view_name = ?'.freeze + SELECT_TYPE = 'SELECT * ' \ 'FROM system_schema.types ' \ @@ -889,6 +912,10 @@ def select_tables(connection) send_select_request(connection, SELECT_TABLES) end + def select_materialized_views(connection) + send_select_request(connection, SELECT_VIEWS) + end + def select_columns(connection) send_select_request(connection, SELECT_COLUMNS) end @@ -923,6 +950,12 @@ def select_keyspace_columns(connection, keyspace_name) send_select_request(connection, SELECT_KEYSPACE_COLUMNS, params, hints) end + def select_keyspace_materialized_views(connection, keyspace_name) + params = [keyspace_name] + hints = [Types.varchar] + send_select_request(connection, SELECT_KEYSPACE_VIEWS, params, hints) + end + def select_keyspace_types(connection, keyspace_name) params = [keyspace_name] hints = [Types.varchar] @@ -948,11 +981,20 @@ def select_table(connection, keyspace_name, table_name) end def select_table_columns(connection, keyspace_name, table_name) + # This is identical to the 2.0 impl, but the SELECT_TABLE_COLUMNS query + # is different between the two, so we need two impls. :( + # Also, this method works fine for finding view columns as well. params = [keyspace_name, table_name] hints = [Types.varchar, Types.varchar] send_select_request(connection, SELECT_TABLE_COLUMNS, params, hints) end + def select_materialized_view(connection, keyspace_name, view_name) + params = [keyspace_name, view_name] + hints = [Types.varchar, Types.varchar] + send_select_request(connection, SELECT_VIEW, params, hints) + end + def select_type(connection, keyspace_name, type_name) params = [keyspace_name, type_name] hints = [Types.varchar, Types.varchar] @@ -988,7 +1030,7 @@ def create_function(function_data, types = nil) @type_parser.parse(argument_type, types).first) end - Function.new(keyspace_name, + Cassandra::Function.new(keyspace_name, function_name, function_lang, function_type, @@ -1066,7 +1108,7 @@ def create_types(rows_types, types) end def create_keyspace(keyspace_data, rows_tables, rows_columns, - rows_types, rows_functions, rows_aggregates) + rows_types, rows_functions, rows_aggregates, rows_views) keyspace_name = keyspace_data['keyspace_name'] replication = create_replication(keyspace_data) @@ -1084,19 +1126,33 @@ def create_keyspace(keyspace_data, rows_tables, rows_columns, aggregates.add_or_update(create_aggregate(row, functions, types)) end + # lookup_columns is a hash of . + # However, views are analogous to tables in this context, so we get + # view columns organized by view-name also. + lookup_columns = map_rows_by(rows_columns, 'table_name') tables = rows_tables.each_with_object({}) do |row, tables| table_name = row['table_name'] tables[table_name] = create_table(row, lookup_columns[table_name], types) end + views = rows_views.each_with_object({}) do |row, views| + view_name = row['view_name'] + base_table = tables[row['base_table_name']] + views[view_name] = create_materialized_view(row, + lookup_columns[view_name], + base_table, + types) + end + Keyspace.new(keyspace_name, keyspace_data['durable_writes'], replication, tables, types, functions, - aggregates) + aggregates, + views) end def create_replication(keyspace_data) @@ -1110,7 +1166,7 @@ def create_compaction_strategy(table_data) options = table_data['compaction'] klass = options.delete('class') klass.slice!('org.apache.cassandra.db.compaction.') - Table::Compaction.new(klass, options) + ColumnContainer::Compaction.new(klass, options) end def create_table_options(table_data, compaction_strategy, is_compact) @@ -1119,7 +1175,7 @@ def create_table_options(table_data, compaction_strategy, is_compact) compression['class'].slice!(COMPRESSION_PACKAGE_PREFIX) end - Table::Options.new( + Cassandra::ColumnContainer::Options.new( table_data['comment'], table_data['read_repair_chance'], table_data['dclocal_read_repair_chance'], @@ -1136,7 +1192,8 @@ def create_table_options(table_data, compaction_strategy, is_compact) table_data['max_index_interval'], compaction_strategy, compression, - is_compact + is_compact, + table_data['crc_check_chance'] ) end @@ -1153,18 +1210,17 @@ def create_table(table_data, rows_columns, types = nil) keyspace_name = table_data['keyspace_name'] table_name = table_data['table_name'] table_flags = table_data['flags'] - table_columns = {} - other_columns = [] - clustering_size = 0 is_dense = table_flags.include?('dense') is_super = table_flags.include?('super') is_compound = table_flags.include?('compound') is_compact = is_super || is_dense || !is_compound + # Separate out partition-key, clustering columns, other columns. partition_key = [] clustering_columns = [] clustering_order = [] + other_columns = [] types ||= @schema.keyspace(keyspace_name).send(:raw_types) rows_columns.each do |row| @@ -1180,39 +1236,69 @@ def create_table(table_data, rows_columns, types = nil) when 'CLUSTERING' clustering_columns[index] = column clustering_order[index] = column.order - - if clustering_size.zero? || index == clustering_size - clustering_size = index + 1 - end else other_columns << column end end - partition_key.each do |column| - table_columns[column.name] = column - end - - clustering_columns.each do |column| - table_columns[column.name] = column - end - - other_columns.each do |column| - table_columns[column.name] = column - end - + # Default the crc_check_chance to 1.0 (Java driver does this, so we + # should, too). + table_data['crc_check_chance'] ||= 1.0 compaction_strategy = create_compaction_strategy(table_data) table_options = create_table_options(table_data, compaction_strategy, is_compact) - Table.new(keyspace_name, + Cassandra::Table.new(keyspace_name, table_name, partition_key, clustering_columns, - table_columns, + other_columns, table_options, clustering_order) end + + def create_materialized_view(view_data, rows_columns, base_table, types = nil) + keyspace_name = view_data['keyspace_name'] + view_name = view_data['view_name'] + include_all_columns = view_data['include_all_columns'] + where_clause = view_data['where_clause'] + + # Separate out partition key, clustering columns, other columns + partition_key = [] + clustering_columns = [] + other_columns = [] + types ||= @schema.keyspace(keyspace_name).send(:raw_types) + + rows_columns.each do |row| + next if row['column_name'].empty? + + column = create_column(row, types) + kind = row['kind'].to_s + index = row['position'] || 0 + + case kind.upcase + when 'PARTITION_KEY' + partition_key[index] = column + when 'CLUSTERING' + clustering_columns[index] = column + else + other_columns << column + end + end + + compaction_strategy = create_compaction_strategy(view_data) + view_options = create_table_options(view_data, compaction_strategy, false) + + MaterializedView.new(keyspace_name, + view_name, + partition_key, + clustering_columns, + other_columns, + view_options, + include_all_columns, + where_clause, + base_table) + end end class MultiVersion @@ -1263,6 +1349,13 @@ def fetch_table(connection, keyspace_name, table_name) return Ione::Future.failed(e) end + def fetch_materialized_view(connection, keyspace_name, view_name) + find_fetcher(connection) + .fetch_materialized_view(connection, keyspace_name, view_name) + rescue => e + return Ione::Future.failed(e) + end + def fetch_type(connection, keyspace_name, type_name) find_fetcher(connection) .fetch_type(connection, keyspace_name, type_name) diff --git a/lib/cassandra/column_container.rb b/lib/cassandra/column_container.rb new file mode 100644 index 000000000..72cfccd33 --- /dev/null +++ b/lib/cassandra/column_container.rb @@ -0,0 +1,257 @@ +# encoding: utf-8 + +#-- +# Copyright 2013-2016 DataStax, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + +module Cassandra + # This class contains all the logic needed for manipulating columns of an object + # (e.g. table or materialized view). It's hidden from the user. + + # @private + class ColumnContainer + class Options + attr_reader :comment, :read_repair_chance, :local_read_repair_chance, + :gc_grace_seconds, :caching, :bloom_filter_fp_chance, + :populate_io_cache_on_flush, :memtable_flush_period_in_ms, + :default_time_to_live, :speculative_retry, :index_interval, + :replicate_on_write, :compaction_strategy, :compact_storage, + :compression_parameters, :crc_check_chance + + def initialize(comment, + read_repair_chance, + local_read_repair_chance, + gc_grace_seconds, + caching, + bloom_filter_fp_chance, + populate_io_cache_on_flush, + memtable_flush_period_in_ms, + default_time_to_live, + speculative_retry, + index_interval, + replicate_on_write, + min_index_interval, + max_index_interval, + compaction_strategy, + compression_parameters, + compact_storage, + crc_check_chance) + @comment = comment + @read_repair_chance = read_repair_chance + @local_read_repair_chance = local_read_repair_chance + @gc_grace_seconds = gc_grace_seconds + @caching = caching + @bloom_filter_fp_chance = bloom_filter_fp_chance + @populate_io_cache_on_flush = populate_io_cache_on_flush + @memtable_flush_period_in_ms = memtable_flush_period_in_ms + @default_time_to_live = default_time_to_live + @speculative_retry = speculative_retry + @index_interval = index_interval + @replicate_on_write = replicate_on_write + @min_index_interval = min_index_interval + @max_index_interval = max_index_interval + @compaction_strategy = compaction_strategy + @compression_parameters = compression_parameters + @compact_storage = compact_storage + @crc_check_chance = crc_check_chance + end + + def replicate_on_write? + @replicate_on_write + end + + def populate_io_cache_on_flush? + @populate_io_cache_on_flush + end + + def compact_storage? + @compact_storage + end + + def to_cql + options = [] + + options << 'COMPACT STORAGE' if @compact_storage + unless @bloom_filter_fp_chance.nil? + options << + "bloom_filter_fp_chance = #{Util.encode_object(@bloom_filter_fp_chance)}" + end + options << "caching = #{Util.encode_object(@caching)}" unless @caching.nil? + options << "comment = #{Util.encode_object(@comment)}" unless @comment.nil? + unless @compaction_strategy.nil? + options << "compaction = #{@compaction_strategy.to_cql}" + end + unless @compression_parameters.nil? + options << "compression = #{Util.encode_object(@compression_parameters)}" + end + unless @local_read_repair_chance.nil? + options << 'dclocal_read_repair_chance = ' \ + "#{Util.encode_object(@local_read_repair_chance)}" + end + unless @default_time_to_live.nil? + options << "default_time_to_live = #{Util.encode_object(@default_time_to_live)}" + end + unless @gc_grace_seconds.nil? + options << "gc_grace_seconds = #{Util.encode_object(@gc_grace_seconds)}" + end + unless @index_interval.nil? + options << "index_interval = #{Util.encode_object(@index_interval)}" + end + unless @max_index_interval.nil? + options << "max_index_interval = #{Util.encode_object(@max_index_interval)}" + end + unless @memtable_flush_period_in_ms.nil? + options << 'memtable_flush_period_in_ms = ' \ + "#{Util.encode_object(@memtable_flush_period_in_ms)}" + end + unless @min_index_interval.nil? + options << "min_index_interval = #{Util.encode_object(@min_index_interval)}" + end + unless @populate_io_cache_on_flush.nil? + options << "populate_io_cache_on_flush = '#{@populate_io_cache_on_flush}'" + end + unless @read_repair_chance.nil? + options << "read_repair_chance = #{Util.encode_object(@read_repair_chance)}" + end + unless @replicate_on_write.nil? + options << "replicate_on_write = '#{@replicate_on_write}'" + end + unless @speculative_retry.nil? + options << "speculative_retry = #{Util.encode_object(@speculative_retry)}" + end + unless @crc_check_chance.nil? + options << 'crc_check_chance = ' \ + "#{Util.encode_object(@crc_check_chance)}" + end + + options.join("\nAND ") + end + + def eql?(other) + other.is_a?(Options) && + @comment == other.comment && + @read_repair_chance == other.read_repair_chance && + @local_read_repair_chance == other.local_read_repair_chance && + @gc_grace_seconds == other.gc_grace_seconds && + @caching == other.caching && + @bloom_filter_fp_chance == other.bloom_filter_fp_chance && + @populate_io_cache_on_flush == other.populate_io_cache_on_flush && + @memtable_flush_period_in_ms == other.memtable_flush_period_in_ms && + @default_time_to_live == other.default_time_to_live && + @speculative_retry == other.speculative_retry && + @index_interval == other.index_interval && + @replicate_on_write == other.replicate_on_write && + @compaction_strategy == other.compaction_strategy && + @compression_parameters == other.compression_parameters && + @compact_storage == other.compact_storage && + @crc_check_chance == other.crc_check_chance + end + alias == eql? + end + + class Compaction + attr_reader :klass, :options + + def initialize(klass, options) + @klass = klass + @options = options + end + + def to_cql + compaction = {'class' => @klass} + compaction.merge!(@options) + + Util.encode_hash(compaction) + end + + def eql?(other) + other.is_a?(Compaction) && + @klass == other.klass && + @options == other.options + end + alias == eql? + end + + attr_reader :keyspace + attr_reader :name + attr_reader :options + attr_reader :partition_key + + attr_reader :clustering_columns + + def initialize(keyspace, + name, + partition_key, + clustering_columns, + other_columns, + options) + @keyspace = keyspace + @name = name + @partition_key = partition_key + @clustering_columns = clustering_columns + @options = options + + # Make one array of all the columns, ordered with partition key, clustering + # columns, then other columns. Make a hash as well, to support random access + # to column metadata for a given column name. + + @columns = @partition_key.dup + @columns.concat(@clustering_columns).concat(other_columns) + @columns_hash = @columns.each_with_object({}) do |col, h| + h[col.name] = col + end + end + + def has_column?(name) + @columns_hash.key?(name) + end + + def column(name) + @columns_hash[name] + end + + def each_column(&block) + if block_given? + @columns.each(&block) + self + else + # We return a dup of the columns so that the caller can manipulate + # the array however they want without affecting the source. + @columns.dup + end + end + alias columns each_column + + def inspect + "#<#{self.class.name}:0x#{object_id.to_s(16)} " \ + "@keyspace=#{@keyspace} @name=#{@name}>" + end + + def eql?(other) + other.is_a?(ColumnContainer) && + @keyspace == other.keyspace && + @name == other.name && + @partition_key == other.partition_key && + @clustering_columns == other.clustering_columns && + @columns == other.raw_columns && + @options == other.options + end + alias == eql? + + def raw_columns + @columns + end + end +end diff --git a/lib/cassandra/keyspace.rb b/lib/cassandra/keyspace.rb index 215c787e6..8d5135dfe 100644 --- a/lib/cassandra/keyspace.rb +++ b/lib/cassandra/keyspace.rb @@ -57,7 +57,8 @@ def initialize(name, tables, types, functions, - aggregates) + aggregates, + views) @name = name @durable_writes = durable_writes @replication = replication @@ -65,6 +66,7 @@ def initialize(name, @types = types @functions = functions @aggregates = aggregates + @views = views end # @return [Boolean] whether durables writes are enabled for this keyspace @@ -100,6 +102,34 @@ def each_table(&block) end alias tables each_table + # @return [Boolean] whether this keyspace has a materialized view with the given name + # @param name [String] materialized view name + def has_materialized_view?(name) + @views.key?(name) + end + + # @return [Cassandra::MaterializedView, nil] a materialized view or nil + # @param name [String] materialized view name + def materialized_view(name) + @views[name] + end + + # Yield or enumerate each materialized view defined in this keyspace + # @overload each_materialized_view + # @yieldparam view [Cassandra::MaterializedView] current materialized view + # @return [Cassandra::Keyspace] self + # @overload each_materialized_view + # @return [Array] a list of materialized views + def each_materialized_view(&block) + if block_given? + @views.each_value(&block) + self + else + @views.values + end + end + alias materialized_views each_materialized_view + # @return [Boolean] whether this keyspace has a user-defined type with the # given name # @param name [String] user-defined type name @@ -228,7 +258,8 @@ def update_table(table) tables, @types, @functions, - @aggregates) + @aggregates, + @views) end # @private @@ -241,7 +272,36 @@ def delete_table(table_name) tables, @types, @functions, - @aggregates) + @aggregates, + @views) + end + + # @private + def update_materialized_view(view) + views = @views.dup + views[view.name] = view + Keyspace.new(@name, + @durable_writes, + @replication, + @tables, + @types, + @functions, + @aggregates, + views) + end + + # @private + def delete_materialized_view(view_name) + views = @views.dup + views.delete(view_name) + Keyspace.new(@name, + @durable_writes, + @replication, + @tables, + @types, + @functions, + @aggregates, + views) end # @private @@ -254,7 +314,8 @@ def update_type(type) @tables, types, @functions, - @aggregates) + @aggregates, + @views) end # @private @@ -267,7 +328,8 @@ def delete_type(type_name) @tables, types, @functions, - @aggregates) + @aggregates, + @views) end # @private @@ -280,7 +342,8 @@ def update_function(function) @tables, @types, functions, - @aggregates) + @aggregates, + @views) end # @private @@ -293,7 +356,8 @@ def delete_function(function_name, function_args) @tables, @types, functions, - @aggregates) + @aggregates, + @views) end # @private @@ -306,7 +370,8 @@ def update_aggregate(aggregate) @tables, @types, @functions, - aggregates) + aggregates, + @views) end # @private @@ -319,7 +384,8 @@ def delete_aggregate(aggregate_name, aggregate_args) @tables, @types, @functions, - aggregates) + aggregates, + @views) end # @private @@ -333,6 +399,11 @@ def raw_tables @tables end + # @private + def raw_materialized_views + @views + end + # @private def raw_types @types diff --git a/lib/cassandra/materialized_view.rb b/lib/cassandra/materialized_view.rb new file mode 100644 index 000000000..1d4625183 --- /dev/null +++ b/lib/cassandra/materialized_view.rb @@ -0,0 +1,125 @@ +# encoding: utf-8 + +#-- +# Copyright 2013-2016 DataStax, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + +require 'forwardable' + +module Cassandra + # Represents a cassandra materialized view + # @see Cassandra::Keyspace#each_materialized_view + # @see Cassandra::Keyspace#materialized_view + class MaterializedView + extend Forwardable + + # @return [Table] the table that this materialized view applies to. + attr_reader :base_table + + # @private + def initialize(keyspace, + name, + partition_key, + clustering_columns, + other_columns, + options, + include_all_columns, + where_clause, + base_table) + @column_container = ColumnContainer.new(keyspace, name, partition_key, clustering_columns, other_columns, options) + @include_all_columns = include_all_columns + @where_clause = where_clause + @base_table = base_table + end + + # @!method name + # @return [String] view name + # + # @!method has_column?(name) + # @param name [String] column name + # @return [Boolean] whether this view has a given column + # + # @!method column(name) + # @param name [String] column name + # @return [Cassandra::Column, nil] a column or nil + # + # @!method each_column(&block) + # Yield or enumerate each column defined in this view + # @overload each_column + # @yieldparam column [Cassandra::Column] current column + # @return [Cassandra::Table] self + # @overload each_column + # @return [Array] a list of columns + # + # @!method columns + # @return [Array] a list of columns + def_delegators :@column_container, :name, 'has_column?', :column, :each_column, :columns + + # @return [String] a cql representation of this materialized view + def to_cql + keyspace_name = Util.escape_name(keyspace) + cql = "CREATE MATERIALIZED VIEW #{keyspace_name}.#{Util.escape_name(name)} AS\nSELECT " + if @include_all_columns + cql << '*' + else + cql << @column_container.raw_columns.map do |column| + Util.escape_name(column.name) + end.join(', ') + end + cql << "\nFROM #{keyspace_name}.#{Util.escape_name(@base_table.name)}" + cql << "\nWHERE #{@where_clause}" if @where_clause + cql << "\nPRIMARY KEY((" + cql << partition_key.map do |column| + Util.escape_name(column.name) + end.join(', ') + cql << ")" + unless clustering_columns.empty? + cql << "," + cql << clustering_columns.map do |column| + Util.escape_name(column.name) + end.join(', ') + end + cql << ")\nWITH #{options.to_cql};" + end + + # @private + def inspect + "#<#{self.class.name}:0x#{object_id.to_s(16)} " \ + "@keyspace=#{keyspace} @name=#{name}>" + end + + # @private + def eql?(other) + other.is_a?(MaterializedView) && + @column_container == other.column_container && + @include_all_columns == other.include_all_columns && + @where_clause == other.where_clause && + @base_table == other.base_table + end + alias == eql? + + private + + # Delegators to easily get to other attributes for use within our class (for to_cql) + def_delegators :@column_container, :keyspace, :partition_key, :clustering_columns, :options + + # We need these accessors for eql? to work, but we don't want random users to + # get these. + + # @private + attr_reader :column_container, :include_all_columns, :where_clause + protected :column_container, :include_all_columns, :where_clause + end +end diff --git a/lib/cassandra/table.rb b/lib/cassandra/table.rb index 050c4f32b..2d15a32ac 100644 --- a/lib/cassandra/table.rb +++ b/lib/cassandra/table.rb @@ -16,232 +16,60 @@ # limitations under the License. #++ +require 'forwardable' + module Cassandra # Represents a cassandra table # @see Cassandra::Keyspace#each_table # @see Cassandra::Keyspace#table class Table - # @private - class Options - attr_reader :comment, :read_repair_chance, :local_read_repair_chance, - :gc_grace_seconds, :caching, :bloom_filter_fp_chance, - :populate_io_cache_on_flush, :memtable_flush_period_in_ms, - :default_time_to_live, :speculative_retry, :index_interval, - :replicate_on_write, :compaction_strategy, :compact_storage, - :compression_parameters - - def initialize(comment, - read_repair_chance, - local_read_repair_chance, - gc_grace_seconds, - caching, - bloom_filter_fp_chance, - populate_io_cache_on_flush, - memtable_flush_period_in_ms, - default_time_to_live, - speculative_retry, - index_interval, - replicate_on_write, - min_index_interval, - max_index_interval, - compaction_strategy, - compression_parameters, - compact_storage) - @comment = comment - @read_repair_chance = read_repair_chance - @local_read_repair_chance = local_read_repair_chance - @gc_grace_seconds = gc_grace_seconds - @caching = caching - @bloom_filter_fp_chance = bloom_filter_fp_chance - @populate_io_cache_on_flush = populate_io_cache_on_flush - @memtable_flush_period_in_ms = memtable_flush_period_in_ms - @default_time_to_live = default_time_to_live - @speculative_retry = speculative_retry - @index_interval = index_interval - @replicate_on_write = replicate_on_write - @min_index_interval = min_index_interval - @max_index_interval = max_index_interval - @compaction_strategy = compaction_strategy - @compression_parameters = compression_parameters - @compact_storage = compact_storage - end - - def replicate_on_write? - @replicate_on_write - end - - def populate_io_cache_on_flush? - @populate_io_cache_on_flush - end - - def compact_storage? - @compact_storage - end - - def to_cql - options = [] - - options << 'COMPACT STORAGE' if @compact_storage - unless @bloom_filter_fp_chance.nil? - options << - "bloom_filter_fp_chance = #{Util.encode_object(@bloom_filter_fp_chance)}" - end - options << "caching = #{Util.encode_object(@caching)}" unless @caching.nil? - options << "comment = #{Util.encode_object(@comment)}" unless @comment.nil? - unless @compaction_strategy.nil? - options << "compaction = #{@compaction_strategy.to_cql}" - end - unless @compression_parameters.nil? - options << "compression = #{Util.encode_object(@compression_parameters)}" - end - unless @local_read_repair_chance.nil? - options << 'dclocal_read_repair_chance = ' \ - "#{Util.encode_object(@local_read_repair_chance)}" - end - unless @default_time_to_live.nil? - options << "default_time_to_live = #{Util.encode_object(@default_time_to_live)}" - end - unless @gc_grace_seconds.nil? - options << "gc_grace_seconds = #{Util.encode_object(@gc_grace_seconds)}" - end - unless @index_interval.nil? - options << "index_interval = #{Util.encode_object(@index_interval)}" - end - unless @max_index_interval.nil? - options << "max_index_interval = #{Util.encode_object(@max_index_interval)}" - end - unless @memtable_flush_period_in_ms.nil? - options << 'memtable_flush_period_in_ms = ' \ - "#{Util.encode_object(@memtable_flush_period_in_ms)}" - end - unless @min_index_interval.nil? - options << "min_index_interval = #{Util.encode_object(@min_index_interval)}" - end - unless @populate_io_cache_on_flush.nil? - options << "populate_io_cache_on_flush = '#{@populate_io_cache_on_flush}'" - end - unless @read_repair_chance.nil? - options << "read_repair_chance = #{Util.encode_object(@read_repair_chance)}" - end - unless @replicate_on_write.nil? - options << "replicate_on_write = '#{@replicate_on_write}'" - end - unless @speculative_retry.nil? - options << "speculative_retry = #{Util.encode_object(@speculative_retry)}" - end - - options.join("\nAND ") - end - - def eql?(other) - other.is_a?(Options) && - @comment == other.comment && - @read_repair_chance == other.read_repair_chance && - @local_read_repair_chance == other.local_read_repair_chance && - @gc_grace_seconds == other.gc_grace_seconds && - @caching == other.caching && - @bloom_filter_fp_chance == other.bloom_filter_fp_chance && - @populate_io_cache_on_flush == other.populate_io_cache_on_flush && - @memtable_flush_period_in_ms == other.memtable_flush_period_in_ms && - @default_time_to_live == other.default_time_to_live && - @speculative_retry == other.speculative_retry && - @index_interval == other.index_interval && - @replicate_on_write == other.replicate_on_write && - @compaction_strategy == other.compaction_strategy && - @compression_parameters == other.compression_parameters && - @compact_storage == other.compact_storage - end - alias == eql? - end - - # @private - class Compaction - attr_reader :klass, :options - - def initialize(klass, options) - @klass = klass - @options = options - end - - def to_cql - compaction = {'class' => @klass} - compaction.merge!(@options) - - Util.encode_hash(compaction) - end - - def eql?(other) - other.is_a?(Compaction) && - @klass == other.klass && - @options == other.options - end - alias == eql? - end - - # @private - attr_reader :keyspace - # @return [String] table name - attr_reader :name - # @private - attr_reader :options - # @private - attr_reader :partition_key + extend Forwardable # @private def initialize(keyspace, name, partition_key, clustering_columns, - columns, + other_columns, options, clustering_order) - @keyspace = keyspace - @name = name - @partition_key = partition_key - @clustering_columns = clustering_columns - @columns = columns - @options = options + @column_container = ColumnContainer.new(keyspace, name, partition_key, clustering_columns, other_columns, options) @clustering_order = clustering_order end - # @param name [String] column name - # @return [Boolean] whether this table has a given column - def has_column?(name) - @columns.key?(name) - end - - # @param name [String] column name - # @return [Cassandra::Column, nil] a column or nil - def column(name) - @columns[name] - end - - # Yield or enumerate each column defined in this table - # @overload each_column - # @yieldparam column [Cassandra::Column] current column - # @return [Cassandra::Table] self - # @overload each_column + # @!method name + # @return [String] table name + # + # @!method has_column?(name) + # @param name [String] column name + # @return [Boolean] whether this table has a given column + # + # @!method column(name) + # @param name [String] column name + # @return [Cassandra::Column, nil] a column or nil + # + # @!method each_column(&block) + # Yield or enumerate each column defined in this table + # @overload each_column + # @yieldparam column [Cassandra::Column] current column + # @return [Cassandra::Table] self + # @overload each_column + # @return [Array] a list of columns + # + # @!method columns # @return [Array] a list of columns - def each_column(&block) - if block_given? - @columns.each_value(&block) - self - else - @columns.values - end - end - alias columns each_column + def_delegators :@column_container, :name, 'has_column?', :column, :each_column, :columns # @return [String] a cql representation of this table def to_cql - cql = "CREATE TABLE #{Util.escape_name(@keyspace)}.#{Util.escape_name(@name)} (\n" + cql = "CREATE TABLE #{Util.escape_name(keyspace)}.#{Util.escape_name(name)} (\n" primary_key = nil - if @partition_key.one? && @clustering_columns.empty? - primary_key = @partition_key.first.name + if partition_key.one? && clustering_columns.empty? + primary_key = partition_key.first.name end first = true - @columns.each do |(_, column)| + @column_container.raw_columns.each do |column| if first first = false else @@ -253,12 +81,12 @@ def to_cql unless primary_key cql << ",\n PRIMARY KEY (" - if @partition_key.one? - cql << @partition_key.first.name + if partition_key.one? + cql << partition_key.first.name else cql << '(' first = true - @partition_key.each do |column| + partition_key.each do |column| if first first = false else @@ -268,7 +96,7 @@ def to_cql end cql << ')' end - @clustering_columns.each do |column| + clustering_columns.each do |column| cql << ", #{column.name}" end cql << ')' @@ -279,7 +107,7 @@ def to_cql if @clustering_order.any? {|o| o != :asc} cql << 'CLUSTERING ORDER BY (' first = true - @clustering_columns.zip(@clustering_order) do |column, order| + clustering_columns.zip(@clustering_order) do |column, order| if first first = false else @@ -290,7 +118,7 @@ def to_cql cql << ")\n AND " end - cql << @options.to_cql.split("\n").join("\n ") + cql << options.to_cql.split("\n").join("\n ") cql << ';' end @@ -298,31 +126,29 @@ def to_cql # @private def inspect "#<#{self.class.name}:0x#{object_id.to_s(16)} " \ - "@keyspace=#{@keyspace} @name=#{@name}>" + "@keyspace=#{keyspace} @name=#{name}>" end # @private def eql?(other) other.is_a?(Table) && - @keyspace == other.keyspace && - @name == other.name && - @partition_key == other.partition_key && - @clustering_columns == other.clustering_columns && - @columns == other.raw_columns && - @options == other.options && + @column_container == other.column_container && @clustering_order == other.clustering_order end alias == eql? private + # Delegators to easily get to other attributes for use within our class (for to_cql). + def_delegators :@column_container, :keyspace, :partition_key, :clustering_columns, :options + # @private def type_to_cql(type, is_frozen) case type.kind when :tuple "frozen <#{type}>" when :udt - if @keyspace == type.keyspace + if keyspace == type.keyspace "frozen <#{Util.escape_name(type.name)}>" else "frozen <#{Util.escape_name(type.keyspace)}.#{Util.escape_name(type.name)}>" @@ -336,14 +162,11 @@ def type_to_cql(type, is_frozen) end end - attr_reader :clustering_columns, :clustering_order - protected :clustering_columns, :clustering_order - - protected + # We need these accessors for eql? to work, but we don't want random users to + # get these. # @private - def raw_columns - @columns - end + attr_reader :column_container, :clustering_order + protected :column_container, :clustering_order end end diff --git a/spec/cassandra/cluster/metadata_spec.rb b/spec/cassandra/cluster/metadata_spec.rb index 7db26b904..bde4eaecf 100644 --- a/spec/cassandra/cluster/metadata_spec.rb +++ b/spec/cassandra/cluster/metadata_spec.rb @@ -22,7 +22,7 @@ class FakeSchema < Cassandra::Cluster::Schema def add_keyspace(name, replication_class, replication_options) replication = Cassandra::Keyspace::Replication.new(replication_class, replication_options) - @keyspaces[name] = Cassandra::Keyspace.new(name, true, replication, {}, {}, {}, {}) + @keyspaces[name] = Cassandra::Keyspace.new(name, true, replication, {}, {}, {}, {}, {}) self end end From f1c9335b78924541c5782f355f6fede0702a9889 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 21 Mar 2016 21:27:17 -0700 Subject: [PATCH 010/196] RUBY-167 - Add materialized view support to the Ruby driver. RUBY-179 - Added crc_check_chance table/view options attribute. RUBY-180 - Column ordering is not deterministic in Table. RUBY-170 - Add extensions table/view options attribute. RUBY-181 - Expose id, keyspace, partition_key, clustering_columns, clustering_order, options attributes of tables and views. --- CHANGELOG.md | 4 +- lib/cassandra/cluster/schema/fetchers.rb | 30 +++-- lib/cassandra/column_container.rb | 158 +++++++++++++++++------ lib/cassandra/keyspace.rb | 8 ++ lib/cassandra/materialized_view.rb | 58 +++------ lib/cassandra/table.rb | 74 +++-------- 6 files changed, 187 insertions(+), 145 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5bb7aec93..21feac5cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,12 @@ Features: * Add protocol_version configuration option to allow the user to force the protocol version to use for communication with nodes. * Add support for materialized views in the schema metadata. -* Add the crc_check_chance attribute to table and view options. +* Add or expose the id, options, keyspace, partition_key, clustering_columns, and clustering_order attributes to table and view schema objects. +* Add crc_check_chance and extensions attributes to ColumnContainer options. Bug Fixes: * [RUBY-161] Protocol version negotiation in mixed version clusters should not fall back to v1 unless it is truly warranted. +* [RUBY-180] Column ordering is not deterministic in Table metadata. Breaking Changes: diff --git a/lib/cassandra/cluster/schema/fetchers.rb b/lib/cassandra/cluster/schema/fetchers.rb index 51f696670..0beef42a9 100644 --- a/lib/cassandra/cluster/schema/fetchers.rb +++ b/lib/cassandra/cluster/schema/fetchers.rb @@ -445,13 +445,14 @@ def create_table(table_data, rows_columns) other_columns << create_column(row) end - Cassandra::Table.new(keyspace_name, + Cassandra::Table.new(@schema.keyspace(keyspace_name), table_name, partition_key, clustering_columns, other_columns, table_options, - clustering_order) + clustering_order, + table_data['id']) end def create_column(column_data) @@ -505,7 +506,8 @@ def create_table_options(table_data, compaction_strategy, is_compact) compaction_strategy, compression_parameters, is_compact, - table_data['crc_check_chance'] + table_data['crc_check_chance'], + table_data['extensions'] ) end end @@ -568,13 +570,14 @@ def create_table(table_data, rows_columns) table_options = create_table_options(table_data, compaction_strategy, is_compact) - Cassandra::Table.new(keyspace_name, + Cassandra::Table.new(@schema.keyspace(keyspace_name), table_name, partition_key, clustering_columns, other_columns, table_options, - clustering_order) + clustering_order, + table_data['id']) end def select_keyspace(connection, keyspace_name) @@ -632,6 +635,7 @@ def create_table_options(table_data, compaction_strategy, is_compact) compression_parameters, is_compact, table_data['crc_check_chance'], + table_data['extensions'] ) end end @@ -704,7 +708,8 @@ def create_table_options(table_data, compaction_strategy, is_compact) compaction_strategy, compression_parameters, is_compact, - table_data['crc_check_chance'] + table_data['crc_check_chance'], + table_data['extensions'] ) end end @@ -1193,7 +1198,8 @@ def create_table_options(table_data, compaction_strategy, is_compact) compaction_strategy, compression, is_compact, - table_data['crc_check_chance'] + table_data['crc_check_chance'], + table_data['extensions'] ) end @@ -1248,13 +1254,14 @@ def create_table(table_data, rows_columns, types = nil) table_options = create_table_options(table_data, compaction_strategy, is_compact) - Cassandra::Table.new(keyspace_name, + Cassandra::Table.new(@schema.keyspace(keyspace_name), table_name, partition_key, clustering_columns, other_columns, table_options, - clustering_order) + clustering_order, + table_data['id']) end def create_materialized_view(view_data, rows_columns, base_table, types = nil) @@ -1289,7 +1296,7 @@ def create_materialized_view(view_data, rows_columns, base_table, types = nil) compaction_strategy = create_compaction_strategy(view_data) view_options = create_table_options(view_data, compaction_strategy, false) - MaterializedView.new(keyspace_name, + MaterializedView.new(@schema.keyspace(keyspace_name), view_name, partition_key, clustering_columns, @@ -1297,7 +1304,8 @@ def create_materialized_view(view_data, rows_columns, base_table, types = nil) view_options, include_all_columns, where_clause, - base_table) + base_table, + view_data['id']) end end diff --git a/lib/cassandra/column_container.rb b/lib/cassandra/column_container.rb index 72cfccd33..6cef21206 100644 --- a/lib/cassandra/column_container.rb +++ b/lib/cassandra/column_container.rb @@ -17,19 +17,49 @@ #++ module Cassandra - # This class contains all the logic needed for manipulating columns of an object - # (e.g. table or materialized view). It's hidden from the user. - - # @private + # This class contains all the logic needed for manipulating columns of an object. class ColumnContainer + # Encapsulates all of the configuration options of a column-container. class Options - attr_reader :comment, :read_repair_chance, :local_read_repair_chance, - :gc_grace_seconds, :caching, :bloom_filter_fp_chance, - :populate_io_cache_on_flush, :memtable_flush_period_in_ms, - :default_time_to_live, :speculative_retry, :index_interval, - :replicate_on_write, :compaction_strategy, :compact_storage, - :compression_parameters, :crc_check_chance + # @return [String] the comment attribute of this column-container. + attr_reader :comment + # @return [Float] the chance with which a read repair is triggered for this column-container. + attr_reader :read_repair_chance + # @return [Float] the cluster local read repair chance for this column-container. + attr_reader :local_read_repair_chance + # @return [Integer] the tombstone garbage collection grace time in seconds for this column-container. + attr_reader :gc_grace_seconds + # @return [Hash] the caching options for this column-container. + attr_reader :caching + # @return [Float] the false positive chance for the Bloom filter of this column-container. + attr_reader :bloom_filter_fp_chance + # @return [Integer] how often (in milliseconds) to flush the memtable of this column-container. + attr_reader :memtable_flush_period_in_ms + # @return [Integer] the default TTL for this column-container. + attr_reader :default_time_to_live + # Return the speculative retry setting of this column-container, which determines how much + # response delay the coordinator node will tolerate from the chosen replica before + # retrying the request on other replicas. This setting can be expressed as a fixed + # delay in ms (e.g. 10ms) or as a percentile indicating "when the response time has + # exceeded the Nth percentile of read response times for this object" (e.g. 99percentile). + # @return [String] the speculative retry setting of this column-container. + attr_reader :speculative_retry + # Return the index interval of this column-container; Cassandra will hold `1/index_interval` of row keys in memory. + # @return [Integer] the index interval of this column-container. May be nil, indicating a default value of 128. + attr_reader :index_interval + # @return [Hash] compression settings + attr_reader :compression + # When compression is enabled, this option defines the probability + # with which checksums for compressed blocks are checked during reads. + # @return [Float] the probability of checking checksums on compressed blocks. + attr_reader :crc_check_chance + # @return [Hash] the extension options of this column-container. + attr_reader :extensions + + # @return [ColumnContainer::Compaction] the compaction strategy of this column-container. + attr_reader :compaction_strategy + # @private def initialize(comment, read_repair_chance, local_read_repair_chance, @@ -45,9 +75,10 @@ def initialize(comment, min_index_interval, max_index_interval, compaction_strategy, - compression_parameters, + compression, compact_storage, - crc_check_chance) + crc_check_chance, + extensions) @comment = comment @read_repair_chance = read_repair_chance @local_read_repair_chance = local_read_repair_chance @@ -63,23 +94,32 @@ def initialize(comment, @min_index_interval = min_index_interval @max_index_interval = max_index_interval @compaction_strategy = compaction_strategy - @compression_parameters = compression_parameters + @compression = compression @compact_storage = compact_storage @crc_check_chance = crc_check_chance + @extensions = extensions end + # Return whether to replicate counter updates to other replicas. It is *strongly* recommended + # that this setting be `true`. Otherwise, counter updates are only written to one replica + # and fault tolerance is sacrificed. + # @return [Boolean] whether to replicate counter updates to other replicas. def replicate_on_write? @replicate_on_write end + # @return [Boolean] whether to populate the I/O cache on flush of this + # column-container. May be nil, indicating a default value of `false`. def populate_io_cache_on_flush? @populate_io_cache_on_flush end + # @return [Boolean] whether this column-container uses compact storage. def compact_storage? @compact_storage end + # @private def to_cql options = [] @@ -93,8 +133,8 @@ def to_cql unless @compaction_strategy.nil? options << "compaction = #{@compaction_strategy.to_cql}" end - unless @compression_parameters.nil? - options << "compression = #{Util.encode_object(@compression_parameters)}" + unless @compression.nil? + options << "compression = #{Util.encode_object(@compression)}" end unless @local_read_repair_chance.nil? options << 'dclocal_read_repair_chance = ' \ @@ -139,6 +179,7 @@ def to_cql options.join("\nAND ") end + # @private def eql?(other) other.is_a?(Options) && @comment == other.comment && @@ -147,98 +188,139 @@ def eql?(other) @gc_grace_seconds == other.gc_grace_seconds && @caching == other.caching && @bloom_filter_fp_chance == other.bloom_filter_fp_chance && - @populate_io_cache_on_flush == other.populate_io_cache_on_flush && + @populate_io_cache_on_flush == other.populate_io_cache_on_flush? && @memtable_flush_period_in_ms == other.memtable_flush_period_in_ms && @default_time_to_live == other.default_time_to_live && @speculative_retry == other.speculative_retry && @index_interval == other.index_interval && - @replicate_on_write == other.replicate_on_write && + @replicate_on_write == other.replicate_on_write? && @compaction_strategy == other.compaction_strategy && - @compression_parameters == other.compression_parameters && - @compact_storage == other.compact_storage && - @crc_check_chance == other.crc_check_chance + @compression == other.compression && + @compact_storage == other.compact_storage? && + @crc_check_chance == other.crc_check_chance && + @extensions == other.extensions end alias == eql? end + # Encapsulates the compaction strategy of a column-container. class Compaction - attr_reader :klass, :options + # @return [String] the name of the Cassandra class that performs compaction. + attr_reader :class_name + # @return [Hash] compaction strategy options + attr_reader :options - def initialize(klass, options) - @klass = klass + # @private + def initialize(class_name, options) + @class_name = class_name @options = options end + # @private def to_cql - compaction = {'class' => @klass} + compaction = {'class' => @class_name} compaction.merge!(@options) Util.encode_hash(compaction) end + # @private def eql?(other) other.is_a?(Compaction) && - @klass == other.klass && + @class_name == other.class_name && @options == other.options end alias == eql? end - attr_reader :keyspace + # @return [String] name of this column-container attr_reader :name + # @return [Cassandra::Uuid] the id of this object in Cassandra. + attr_reader :id + # @return [Cassandra::Keyspace] the keyspace that this column-container belongs to. + attr_reader :keyspace + # @return [ColumnContainer::Options] collection of configuration options of this column-container. attr_reader :options + # @return [Array] ordered list of column-names that make up the partition-key. attr_reader :partition_key - + # @return [Array] ordered list of column-names that make up the clustering-columns. attr_reader :clustering_columns + # @return [Array] primary key of this column-container. It's the combination of `partition_key` and `clustering_columns`. + # @note This composition produces a flat list, so it will not be possible for the caller to distinguish partition-key columns from clustering-columns. + attr_reader :primary_key + # @private def initialize(keyspace, name, partition_key, clustering_columns, other_columns, - options) + options, + id) @keyspace = keyspace - @name = name - @partition_key = partition_key - @clustering_columns = clustering_columns + @name = name.freeze + @partition_key = partition_key.freeze + @clustering_columns = clustering_columns.freeze @options = options + @id = id # Make one array of all the columns, ordered with partition key, clustering # columns, then other columns. Make a hash as well, to support random access - # to column metadata for a given column name. + # to column metadata for a given column name. Save off the primary key (which + # is partition-key + clustering-columns) while we're at it. - @columns = @partition_key.dup - @columns.concat(@clustering_columns).concat(other_columns) + @primary_key = @partition_key.dup.concat(@clustering_columns).freeze + @columns = @partition_key.dup.concat(other_columns).freeze @columns_hash = @columns.each_with_object({}) do |col, h| h[col.name] = col end end + # @param name [String] column name + # @return [Boolean] whether this column-container has a given column def has_column?(name) @columns_hash.key?(name) end + # @param name [String] column name + # @return [Cassandra::Column, nil] a column or nil def column(name) @columns_hash[name] end + # Yield or enumerate each column defined in this column-container + # @overload each_column + # @yieldparam column [Cassandra::Column] current column + # @return [Cassandra::ColumnContainer] self + # @overload each_column + # @return [Array] a list of columns def each_column(&block) if block_given? @columns.each(&block) self else - # We return a dup of the columns so that the caller can manipulate - # the array however they want without affecting the source. - @columns.dup + @columns end end alias columns each_column + # @private + # keyspace may be nil because when this object was constructed, we didn't have + # its keyspace constructed yet. So allow updating keyspace if it's nil, thus + # allowing fetchers to create keyspace, table/view, and hook them together without + # worrying about chickens and eggs. + # NOTE: Ignore the set request if the keyspace attribute is already set. + def set_keyspace(keyspace) + @keyspace = keyspace.freeze unless @keyspace + end + + # @private def inspect "#<#{self.class.name}:0x#{object_id.to_s(16)} " \ "@keyspace=#{@keyspace} @name=#{@name}>" end + # @private def eql?(other) other.is_a?(ColumnContainer) && @keyspace == other.keyspace && @@ -250,8 +332,10 @@ def eql?(other) end alias == eql? + # @private def raw_columns @columns end + protected :raw_columns end end diff --git a/lib/cassandra/keyspace.rb b/lib/cassandra/keyspace.rb index 8d5135dfe..730bc49ec 100644 --- a/lib/cassandra/keyspace.rb +++ b/lib/cassandra/keyspace.rb @@ -67,6 +67,14 @@ def initialize(name, @functions = functions @aggregates = aggregates @views = views + + # Set the keyspace attribute on the tables and views. + @tables.each_value do |t| + t.set_keyspace(self) + end + @views.each_value do |v| + v.set_keyspace(self) + end end # @return [Boolean] whether durables writes are enabled for this keyspace diff --git a/lib/cassandra/materialized_view.rb b/lib/cassandra/materialized_view.rb index 1d4625183..800d24da1 100644 --- a/lib/cassandra/materialized_view.rb +++ b/lib/cassandra/materialized_view.rb @@ -16,14 +16,11 @@ # limitations under the License. #++ -require 'forwardable' - module Cassandra # Represents a cassandra materialized view # @see Cassandra::Keyspace#each_materialized_view # @see Cassandra::Keyspace#materialized_view - class MaterializedView - extend Forwardable + class MaterializedView < ColumnContainer # @return [Table] the table that this materialized view applies to. attr_reader :base_table @@ -37,73 +34,51 @@ def initialize(keyspace, options, include_all_columns, where_clause, - base_table) - @column_container = ColumnContainer.new(keyspace, name, partition_key, clustering_columns, other_columns, options) + base_table, + id) + super(keyspace, name, partition_key, clustering_columns, other_columns, options, id) @include_all_columns = include_all_columns @where_clause = where_clause @base_table = base_table end - # @!method name - # @return [String] view name - # - # @!method has_column?(name) - # @param name [String] column name - # @return [Boolean] whether this view has a given column - # - # @!method column(name) - # @param name [String] column name - # @return [Cassandra::Column, nil] a column or nil - # - # @!method each_column(&block) - # Yield or enumerate each column defined in this view - # @overload each_column - # @yieldparam column [Cassandra::Column] current column - # @return [Cassandra::Table] self - # @overload each_column - # @return [Array] a list of columns - # - # @!method columns - # @return [Array] a list of columns - def_delegators :@column_container, :name, 'has_column?', :column, :each_column, :columns - # @return [String] a cql representation of this materialized view def to_cql - keyspace_name = Util.escape_name(keyspace) - cql = "CREATE MATERIALIZED VIEW #{keyspace_name}.#{Util.escape_name(name)} AS\nSELECT " + keyspace_name = Util.escape_name(@keyspace.name) + cql = "CREATE MATERIALIZED VIEW #{keyspace_name}.#{Util.escape_name(@name)} AS\nSELECT " if @include_all_columns cql << '*' else - cql << @column_container.raw_columns.map do |column| + cql << @columns.map do |column| Util.escape_name(column.name) end.join(', ') end cql << "\nFROM #{keyspace_name}.#{Util.escape_name(@base_table.name)}" cql << "\nWHERE #{@where_clause}" if @where_clause cql << "\nPRIMARY KEY((" - cql << partition_key.map do |column| + cql << @partition_key.map do |column| Util.escape_name(column.name) end.join(', ') cql << ")" - unless clustering_columns.empty? + unless @clustering_columns.empty? cql << "," - cql << clustering_columns.map do |column| + cql << @clustering_columns.map do |column| Util.escape_name(column.name) end.join(', ') end - cql << ")\nWITH #{options.to_cql};" + cql << ")\nWITH #{@options.to_cql};" end # @private def inspect "#<#{self.class.name}:0x#{object_id.to_s(16)} " \ - "@keyspace=#{keyspace} @name=#{name}>" + "@keyspace=#{@keyspace.name} @name=#{@name}>" end # @private def eql?(other) other.is_a?(MaterializedView) && - @column_container == other.column_container && + super.eql?(other) && @include_all_columns == other.include_all_columns && @where_clause == other.where_clause && @base_table == other.base_table @@ -112,14 +87,11 @@ def eql?(other) private - # Delegators to easily get to other attributes for use within our class (for to_cql) - def_delegators :@column_container, :keyspace, :partition_key, :clustering_columns, :options - # We need these accessors for eql? to work, but we don't want random users to # get these. # @private - attr_reader :column_container, :include_all_columns, :where_clause - protected :column_container, :include_all_columns, :where_clause + attr_reader :include_all_columns, :where_clause + protected :include_all_columns, :where_clause end end diff --git a/lib/cassandra/table.rb b/lib/cassandra/table.rb index 2d15a32ac..f2e63bd08 100644 --- a/lib/cassandra/table.rb +++ b/lib/cassandra/table.rb @@ -16,14 +16,14 @@ # limitations under the License. #++ -require 'forwardable' - module Cassandra # Represents a cassandra table # @see Cassandra::Keyspace#each_table # @see Cassandra::Keyspace#table - class Table - extend Forwardable + class Table < ColumnContainer + # @return [Array] an array of order values (`:asc` or `:desc`) that apply to the + # `clustering_columns` array. + attr_reader :clustering_order # @private def initialize(keyspace, @@ -32,48 +32,26 @@ def initialize(keyspace, clustering_columns, other_columns, options, - clustering_order) - @column_container = ColumnContainer.new(keyspace, name, partition_key, clustering_columns, other_columns, options) + clustering_order, + id) + super(keyspace, name, partition_key, clustering_columns, other_columns, options, id) @clustering_order = clustering_order end - # @!method name - # @return [String] table name - # - # @!method has_column?(name) - # @param name [String] column name - # @return [Boolean] whether this table has a given column - # - # @!method column(name) - # @param name [String] column name - # @return [Cassandra::Column, nil] a column or nil - # - # @!method each_column(&block) - # Yield or enumerate each column defined in this table - # @overload each_column - # @yieldparam column [Cassandra::Column] current column - # @return [Cassandra::Table] self - # @overload each_column - # @return [Array] a list of columns - # - # @!method columns - # @return [Array] a list of columns - def_delegators :@column_container, :name, 'has_column?', :column, :each_column, :columns - # @return [String] a cql representation of this table def to_cql - cql = "CREATE TABLE #{Util.escape_name(keyspace)}.#{Util.escape_name(name)} (\n" + cql = "CREATE TABLE #{Util.escape_name(@keyspace.name)}.#{Util.escape_name(@name)} (\n" primary_key = nil - if partition_key.one? && clustering_columns.empty? - primary_key = partition_key.first.name + if @partition_key.one? && @clustering_columns.empty? + primary_key = @partition_key.first.name end first = true - @column_container.raw_columns.each do |column| + @columns.each do |column| if first first = false else - cql << ",\n" unless first + cql << ",\n" end cql << " #{column.name} #{type_to_cql(column.type, column.frozen?)}" cql << ' PRIMARY KEY' if primary_key && column.name == primary_key @@ -81,12 +59,12 @@ def to_cql unless primary_key cql << ",\n PRIMARY KEY (" - if partition_key.one? - cql << partition_key.first.name + if @partition_key.one? + cql << @partition_key.first.name else cql << '(' first = true - partition_key.each do |column| + @partition_key.each do |column| if first first = false else @@ -96,7 +74,7 @@ def to_cql end cql << ')' end - clustering_columns.each do |column| + @clustering_columns.each do |column| cql << ", #{column.name}" end cql << ')' @@ -107,7 +85,7 @@ def to_cql if @clustering_order.any? {|o| o != :asc} cql << 'CLUSTERING ORDER BY (' first = true - clustering_columns.zip(@clustering_order) do |column, order| + @clustering_columns.zip(@clustering_order) do |column, order| if first first = false else @@ -118,7 +96,7 @@ def to_cql cql << ")\n AND " end - cql << options.to_cql.split("\n").join("\n ") + cql << @options.to_cql.split("\n").join("\n ") cql << ';' end @@ -126,29 +104,26 @@ def to_cql # @private def inspect "#<#{self.class.name}:0x#{object_id.to_s(16)} " \ - "@keyspace=#{keyspace} @name=#{name}>" + "@keyspace=#{@keyspace.name} @name=#{@name}>" end # @private def eql?(other) other.is_a?(Table) && - @column_container == other.column_container && + super.eql?(other) && @clustering_order == other.clustering_order end alias == eql? private - # Delegators to easily get to other attributes for use within our class (for to_cql). - def_delegators :@column_container, :keyspace, :partition_key, :clustering_columns, :options - # @private def type_to_cql(type, is_frozen) case type.kind when :tuple "frozen <#{type}>" when :udt - if keyspace == type.keyspace + if @keyspace.name == type.keyspace "frozen <#{Util.escape_name(type.name)}>" else "frozen <#{Util.escape_name(type.keyspace)}.#{Util.escape_name(type.name)}>" @@ -161,12 +136,5 @@ def type_to_cql(type, is_frozen) end end end - - # We need these accessors for eql? to work, but we don't want random users to - # get these. - - # @private - attr_reader :column_container, :clustering_order - protected :column_container, :clustering_order end end From 6eb216a80111ca53aaa7219da60a8b70ae1252fb Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Tue, 22 Mar 2016 10:38:17 -0700 Subject: [PATCH 011/196] * Fixed fetchers test failures * Removed unnecessary inspect methods from Table and MaterializedView * Fixed ColumnContainer.inspect * Fixed bug in table/view metadata refresh. --- lib/cassandra/cluster/schema.rb | 4 ++-- lib/cassandra/column_container.rb | 4 ++-- lib/cassandra/materialized_view.rb | 6 ------ lib/cassandra/table.rb | 6 ------ spec/cassandra/cluster/schema/fetchers_spec.rb | 4 ++++ 5 files changed, 8 insertions(+), 16 deletions(-) diff --git a/lib/cassandra/cluster/schema.rb b/lib/cassandra/cluster/schema.rb index e266267ba..571573eb3 100644 --- a/lib/cassandra/cluster/schema.rb +++ b/lib/cassandra/cluster/schema.rb @@ -126,7 +126,7 @@ def delete_keyspace(keyspace_name) end def replace_table(table) - keyspace = @keyspaces[table.keyspace] + keyspace = table.keyspace return self unless keyspace @@ -170,7 +170,7 @@ def delete_table(keyspace_name, table_name) end def replace_materialized_view(view) - keyspace = @keyspaces[view.keyspace] + keyspace = view.keyspace return self unless keyspace diff --git a/lib/cassandra/column_container.rb b/lib/cassandra/column_container.rb index 6cef21206..cd38abee7 100644 --- a/lib/cassandra/column_container.rb +++ b/lib/cassandra/column_container.rb @@ -270,7 +270,7 @@ def initialize(keyspace, # is partition-key + clustering-columns) while we're at it. @primary_key = @partition_key.dup.concat(@clustering_columns).freeze - @columns = @partition_key.dup.concat(other_columns).freeze + @columns = @primary_key.dup.concat(other_columns).freeze @columns_hash = @columns.each_with_object({}) do |col, h| h[col.name] = col end @@ -317,7 +317,7 @@ def set_keyspace(keyspace) # @private def inspect "#<#{self.class.name}:0x#{object_id.to_s(16)} " \ - "@keyspace=#{@keyspace} @name=#{@name}>" + "@keyspace=#{@keyspace.name} @name=#{@name}>" end # @private diff --git a/lib/cassandra/materialized_view.rb b/lib/cassandra/materialized_view.rb index 800d24da1..be30688e4 100644 --- a/lib/cassandra/materialized_view.rb +++ b/lib/cassandra/materialized_view.rb @@ -69,12 +69,6 @@ def to_cql cql << ")\nWITH #{@options.to_cql};" end - # @private - def inspect - "#<#{self.class.name}:0x#{object_id.to_s(16)} " \ - "@keyspace=#{@keyspace.name} @name=#{@name}>" - end - # @private def eql?(other) other.is_a?(MaterializedView) && diff --git a/lib/cassandra/table.rb b/lib/cassandra/table.rb index f2e63bd08..68d395db8 100644 --- a/lib/cassandra/table.rb +++ b/lib/cassandra/table.rb @@ -101,12 +101,6 @@ def to_cql cql << ';' end - # @private - def inspect - "#<#{self.class.name}:0x#{object_id.to_s(16)} " \ - "@keyspace=#{@keyspace.name} @name=#{@name}>" - end - # @private def eql?(other) other.is_a?(Table) && diff --git a/spec/cassandra/cluster/schema/fetchers_spec.rb b/spec/cassandra/cluster/schema/fetchers_spec.rb index 472b1fb63..5a24a6b0e 100644 --- a/spec/cassandra/cluster/schema/fetchers_spec.rb +++ b/spec/cassandra/cluster/schema/fetchers_spec.rb @@ -37,6 +37,10 @@ module Fetchers let(:cluster_schema) { double('schema') } subject { klass.new(schema_type_parser, cluster_schema) } + before do + allow(cluster_schema).to receive(:keyspace).and_return(nil) + end + describe('#fetch') do before do allow(connection).to receive(:send_request) do |r| From 847e606341d4f42a1d787c9e7752d00a2734a01a Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Wed, 23 Mar 2016 15:40:38 -0700 Subject: [PATCH 012/196] RUBY-178 - Add support for C* index metadata --- CHANGELOG.md | 1 + features/basics/schema_metadata.feature | 66 +++++++++ lib/cassandra.rb | 1 + lib/cassandra/cluster/schema/fetchers.rb | 170 +++++++++++++++++------ lib/cassandra/column.rb | 21 +-- lib/cassandra/column_container.rb | 8 +- lib/cassandra/index.rb | 111 +++++++++++++++ lib/cassandra/table.rb | 41 +++++- 8 files changed, 348 insertions(+), 71 deletions(-) create mode 100644 lib/cassandra/index.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 740b6a5af..270d414b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ Features: * Add protocol_version configuration option to allow the user to force the protocol version to use for communication with nodes. * Expose listen_address and broadcast_address in `Cassandra::Host` if available. * Add support for materialized views in the schema metadata. +* Add support for Cassandra indexes in the schema metadata. * Add or expose the id, options, keyspace, partition_key, clustering_columns, and clustering_order attributes to table and view schema objects. * Add crc_check_chance and extensions attributes to ColumnContainer options. diff --git a/features/basics/schema_metadata.feature b/features/basics/schema_metadata.feature index 3112a7234..75f45baad 100644 --- a/features/basics/schema_metadata.feature +++ b/features/basics/schema_metadata.feature @@ -46,6 +46,72 @@ Feature: Schema Metadata ) """ + Scenario: Getting index metadata + Given the following schema: + """cql + CREATE KEYSPACE simplex WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 3}; + CREATE TABLE simplex.test_table (f1 int primary key, f2 int); + CREATE INDEX ind1 ON simplex.test_table (f2); + """ + And the following example: + """ruby + require 'cassandra' + + cluster = Cassandra.cluster + + cluster.keyspace('simplex').table('test_table').each_index do |index| + puts index.to_cql + end + """ + When it is executed + Then its output should contain: + """cql + CREATE INDEX "ind1" ON simplex.test_table (f2); + """ + + @cassandra-version-specific @cassandra-version-3.0 + Scenario: Getting materialized view metadata + Given the following schema: + """cql + CREATE KEYSPACE simplex WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 3}; + CREATE TABLE simplex.test_table (f1 int PRIMARY KEY, f2 int, f3 int) ; + CREATE MATERIALIZED VIEW simplex.test_view AS + SELECT f1, f2 FROM simplex.test_table WHERE f2 IS NOT NULL + PRIMARY KEY (f1, f2); + """ + And the following example: + """ruby + require 'cassandra' + + cluster = Cassandra.cluster + + view = cluster.keyspace('simplex').materialized_view('test_view') + puts view.to_cql + """ + When it is executed + Then its output should contain: + """cql + CREATE MATERIALIZED VIEW simplex.test_view AS + SELECT "f1", "f2" + FROM simplex.test_table + WHERE f2 IS NOT NULL + PRIMARY KEY(("f1"),"f2") + WITH bloom_filter_fp_chance = 0.01 + AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} + AND comment = '' + AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} + AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} + AND dclocal_read_repair_chance = 0.1 + AND default_time_to_live = 0 + AND gc_grace_seconds = 864000 + AND max_index_interval = 2048 + AND memtable_flush_period_in_ms = 0 + AND min_index_interval = 128 + AND read_repair_chance = 0.0 + AND speculative_retry = '99PERCENTILE' + AND crc_check_chance = 1.0; + """ + @cassandra-version-specific @cassandra-version-2.1 Scenario: Getting user-defined type metadata Given the following schema: diff --git a/lib/cassandra.rb b/lib/cassandra.rb index 45714dbb8..b0a5059fc 100644 --- a/lib/cassandra.rb +++ b/lib/cassandra.rb @@ -775,6 +775,7 @@ def self.validate_and_massage_options(options) require 'cassandra/table' require 'cassandra/materialized_view' require 'cassandra/keyspace' +require 'cassandra/index' require 'cassandra/execution/info' require 'cassandra/execution/options' diff --git a/lib/cassandra/cluster/schema/fetchers.rb b/lib/cassandra/cluster/schema/fetchers.rb index 0beef42a9..c65f82284 100644 --- a/lib/cassandra/cluster/schema/fetchers.rb +++ b/lib/cassandra/cluster/schema/fetchers.rb @@ -32,10 +32,11 @@ def fetch(connection) select_types(connection), select_functions(connection), select_aggregates(connection), - select_materialized_views(connection)) + select_materialized_views(connection), + select_indexes(connection)) .map do |(rows_keyspaces, rows_tables, rows_columns, rows_types, rows_functions, rows_aggregates, - rows_views)| + rows_views, rows_indexes)| lookup_tables = map_rows_by(rows_tables, 'keyspace_name') lookup_columns = map_rows_by(rows_columns, 'keyspace_name') @@ -43,6 +44,7 @@ def fetch(connection) lookup_functions = map_rows_by(rows_functions, 'keyspace_name') lookup_aggregates = map_rows_by(rows_aggregates, 'keyspace_name') lookup_views = map_rows_by(rows_views, 'keyspace_name') + lookup_indexes = map_rows_by(rows_indexes, 'keyspace_name') rows_keyspaces.map do |keyspace_data| name = keyspace_data['keyspace_name'] @@ -53,7 +55,8 @@ def fetch(connection) lookup_types[name], lookup_functions[name], lookup_aggregates[name], - lookup_views[name]) + lookup_views[name], + lookup_indexes[name]) end end end @@ -65,10 +68,11 @@ def fetch_keyspace(connection, keyspace_name) select_keyspace_types(connection, keyspace_name), select_keyspace_functions(connection, keyspace_name), select_keyspace_aggregates(connection, keyspace_name), - select_keyspace_materialized_views(connection, keyspace_name)) + select_keyspace_materialized_views(connection, keyspace_name), + select_keyspace_indexes(connection, keyspace_name)) .map do |(rows_keyspaces, rows_tables, rows_columns, rows_types, rows_functions, rows_aggregates, - rows_views)| + rows_views, rows_indexes)| if rows_keyspaces.empty? nil else @@ -78,20 +82,23 @@ def fetch_keyspace(connection, keyspace_name) rows_types, rows_functions, rows_aggregates, - rows_views) + rows_views, + rows_indexes) end end end def fetch_table(connection, keyspace_name, table_name) Ione::Future.all(select_table(connection, keyspace_name, table_name), - select_table_columns(connection, keyspace_name, table_name)) - .map do |(rows_tables, rows_columns)| + select_table_columns(connection, keyspace_name, table_name), + select_table_indexes(connection, keyspace_name, table_name)) + .map do |(rows_tables, rows_columns, rows_indexes)| if rows_tables.empty? nil else create_table(rows_tables.first, - rows_columns) + rows_columns, + rows_indexes) end end end @@ -163,6 +170,10 @@ def select_columns(connection) FUTURE_EMPTY_LIST end + def select_indexes(connection) + FUTURE_EMPTY_LIST + end + def select_types(connection) FUTURE_EMPTY_LIST end @@ -191,6 +202,10 @@ def select_keyspace_columns(connection, keyspace_name) FUTURE_EMPTY_LIST end + def select_keyspace_indexes(connection, keyspace_name) + FUTURE_EMPTY_LIST + end + def select_keyspace_types(connection, keyspace_name) FUTURE_EMPTY_LIST end @@ -215,6 +230,10 @@ def select_table_columns(connection, keyspace_name, table_name) FUTURE_EMPTY_LIST end + def select_table_indexes(connection, keyspace_name, table_name) + FUTURE_EMPTY_LIST + end + def select_type(connection, keyspace_name, type_name) FUTURE_EMPTY_LIST end @@ -334,7 +353,7 @@ def create_replication(keyspace_data) def create_keyspace(keyspace_data, rows_tables, rows_columns, rows_types, rows_functions, rows_aggregates, - rows_views) + rows_views, rows_indexes) keyspace_name = keyspace_data['keyspace_name'] replication = create_replication(keyspace_data) types = rows_types.each_with_object({}) do |row, types| @@ -355,7 +374,8 @@ def create_keyspace(keyspace_data, rows_tables, rows_columns, lookup_columns = map_rows_by(rows_columns, 'columnfamily_name') tables = rows_tables.each_with_object({}) do |row, tables| table_name = row['columnfamily_name'] - tables[table_name] = create_table(row, lookup_columns[table_name]) + # rows_indexes is nil for C* < 3.0. + tables[table_name] = create_table(row, lookup_columns[table_name], nil) end Keyspace.new(keyspace_name, @@ -368,7 +388,7 @@ def create_keyspace(keyspace_data, rows_tables, rows_columns, {}) end - def create_table(table_data, rows_columns) + def create_table(table_data, rows_columns, rows_indexes) keyspace_name = table_data['keyspace_name'] table_name = table_data['columnfamily_name'] key_validator = @type_parser.parse(table_data['key_validator']) @@ -417,7 +437,7 @@ def create_table(table_data, rows_columns) key_validator.results.each_with_index do |(type, order, is_frozen), i| key_alias = key_aliases.fetch(i) { i.zero? ? "key" : "key#{i + 1}" } - partition_key[i] = Column.new(key_alias, type, order, nil, false, is_frozen) + partition_key[i] = Column.new(key_alias, type, order, false, is_frozen) end clustering_size.times do |i| @@ -425,7 +445,7 @@ def create_table(table_data, rows_columns) type, order, is_frozen = comparator.results.fetch(i) clustering_columns[i] = - Column.new(column_alias, type, order, nil, false, is_frozen) + Column.new(column_alias, type, order, false, is_frozen) clustering_order[i] = order end @@ -437,12 +457,19 @@ def create_table(table_data, rows_columns) type, order, is_frozen = @type_parser.parse(table_data['default_validator']).results.first other_columns << - Column.new(value_alias, type, order, nil, false, is_frozen) + Column.new(value_alias, type, order, false, is_frozen) end end + # In C* 1.2.x, index info is in the column metadata; rows_indexes is nil. + indexes = [] rows_columns.each do |row| - other_columns << create_column(row) + column = create_column(row) + other_columns << column + + unless row['index_type'].nil? + indexes << create_index(column, row) + end end Cassandra::Table.new(@schema.keyspace(keyspace_name), @@ -452,7 +479,31 @@ def create_table(table_data, rows_columns) other_columns, table_options, clustering_order, - table_data['id']) + table_data['id'], + indexes) + end + + def create_index(column, row_column) + # Most of this logic was taken from the Java driver. + options = {} + # For some versions of C*, this field could have a literal string 'null' value. + if !row_column['index_options'].nil? && row_column['index_options'] != 'null' && !row_column['index_options'].empty? + options = ::JSON.load(row_column['index_options']) + end + column_name = Util.escape_name(column.name) + target = if options.has_key?('index_keys') + "keys(#{column_name})" + elsif options.has_key?('index_keys_and_values') + "entries(#{column_name})" + elsif column.frozen? && (column.type == Cassandra::Types::Set || + column.type == Cassandra::Types::List || + column.type == Cassandra::Types::Map) + "full(#{column_name})" + else + column_name + end + + Cassandra::Index.new(row_column['index_name'], row_column['index_type'].downcase.to_sym, target, options) end def create_column(column_data) @@ -460,19 +511,7 @@ def create_column(column_data) is_static = (column_data['type'] == 'STATIC') type, order, is_frozen = @type_parser.parse(column_data['validator']).results.first - - if column_data['index_type'].nil? - index = nil - elsif column_data['index_type'].to_s.upcase == 'CUSTOM' || - !column_data['index_options'] - index = Column::Index.new(column_data['index_name']) - else - options = ::JSON.load(column_data['index_options']) - index = Column::Index.new(column_data['index_name'], - options && options['class_name']) - end - - Column.new(name, type, order, index, is_static, is_frozen) + Column.new(name, type, order, is_static, is_frozen) end def create_compaction_strategy(table_data) @@ -530,7 +569,7 @@ class V2_0_x < V1_2_x private - def create_table(table_data, rows_columns) + def create_table(table_data, rows_columns, rows_indexes) keyspace_name = table_data['keyspace_name'] table_name = table_data['columnfamily_name'] comparator = @type_parser.parse(table_data['comparator']) @@ -542,26 +581,32 @@ def create_table(table_data, rows_columns) clustering_order = [] other_columns = [] + # In C* 2.0.x, index info is in the column metadata; rows_indexes is nil. + indexes = [] rows_columns.each do |row| next if row['column_name'].empty? column = create_column(row) type = row['type'].to_s - index = row['component_index'] || 0 + ind = row['component_index'] || 0 case type.upcase when 'PARTITION_KEY' - partition_key[index] = column + partition_key[ind] = column when 'CLUSTERING_KEY' - clustering_columns[index] = column - clustering_order[index] = column.order + clustering_columns[ind] = column + clustering_order[ind] = column.order - if clustering_size.zero? || index == clustering_size - clustering_size = index + 1 + if clustering_size.zero? || ind == clustering_size + clustering_size = ind + 1 end else other_columns << column end + + unless row['index_type'].nil? + indexes << create_index(column, row) + end end compaction_strategy = create_compaction_strategy(table_data) @@ -577,7 +622,8 @@ def create_table(table_data, rows_columns) other_columns, table_options, clustering_order, - table_data['id']) + table_data['id'], + indexes) end def select_keyspace(connection, keyspace_name) @@ -852,6 +898,8 @@ class V3_0_x < V2_2_x 'SELECT * FROM system_schema.keyspaces WHERE keyspace_name = ?'.freeze SELECT_KEYSPACE_TABLES = 'SELECT * FROM system_schema.tables WHERE keyspace_name = ?'.freeze + SELECT_KEYSPACE_INDEXES = + 'SELECT * FROM system_schema.indexes WHERE keyspace_name = ?'.freeze SELECT_KEYSPACE_COLUMNS = 'SELECT * FROM system_schema.columns WHERE keyspace_name = ?'.freeze SELECT_KEYSPACE_VIEWS = @@ -871,6 +919,10 @@ class V3_0_x < V2_2_x 'SELECT * ' \ 'FROM system_schema.columns ' \ 'WHERE keyspace_name = ? AND table_name = ?'.freeze + SELECT_TABLE_INDEXES = + 'SELECT * ' \ + 'FROM system_schema.indexes ' \ + 'WHERE keyspace_name = ? AND table_name = ?'.freeze SELECT_VIEW = 'SELECT * ' \ @@ -917,6 +969,10 @@ def select_tables(connection) send_select_request(connection, SELECT_TABLES) end + def select_indexes(connection) + send_select_request(connection, SELECT_INDEXES) + end + def select_materialized_views(connection) send_select_request(connection, SELECT_VIEWS) end @@ -955,6 +1011,12 @@ def select_keyspace_columns(connection, keyspace_name) send_select_request(connection, SELECT_KEYSPACE_COLUMNS, params, hints) end + def select_keyspace_indexes(connection, keyspace_name) + params = [keyspace_name] + hints = [Types.varchar] + send_select_request(connection, SELECT_KEYSPACE_INDEXES, params, hints) + end + def select_keyspace_materialized_views(connection, keyspace_name) params = [keyspace_name] hints = [Types.varchar] @@ -994,6 +1056,12 @@ def select_table_columns(connection, keyspace_name, table_name) send_select_request(connection, SELECT_TABLE_COLUMNS, params, hints) end + def select_table_indexes(connection, keyspace_name, table_name) + params = [keyspace_name, table_name] + hints = [Types.varchar, Types.varchar] + send_select_request(connection, SELECT_TABLE_INDEXES, params, hints) + end + def select_materialized_view(connection, keyspace_name, view_name) params = [keyspace_name, view_name] hints = [Types.varchar, Types.varchar] @@ -1112,8 +1180,8 @@ def create_types(rows_types, types) end end - def create_keyspace(keyspace_data, rows_tables, rows_columns, - rows_types, rows_functions, rows_aggregates, rows_views) + def create_keyspace(keyspace_data, rows_tables, rows_columns, rows_types, + rows_functions, rows_aggregates, rows_views, rows_indexes) keyspace_name = keyspace_data['keyspace_name'] replication = create_replication(keyspace_data) @@ -1136,9 +1204,11 @@ def create_keyspace(keyspace_data, rows_tables, rows_columns, # view columns organized by view-name also. lookup_columns = map_rows_by(rows_columns, 'table_name') + lookup_indexes = map_rows_by(rows_indexes, 'table_name') tables = rows_tables.each_with_object({}) do |row, tables| table_name = row['table_name'] - tables[table_name] = create_table(row, lookup_columns[table_name], types) + tables[table_name] = create_table(row, lookup_columns[table_name], + lookup_indexes[table_name], types) end views = rows_views.each_with_object({}) do |row, views| @@ -1209,10 +1279,10 @@ def create_column(column_data, types) order = column_data['clustering_order'] == 'desc' ? :desc : :asc type, is_frozen = @type_parser.parse(column_data['type'], types) - Column.new(name, type, order, nil, is_static, is_frozen) + Column.new(name, type, order, is_static, is_frozen) end - def create_table(table_data, rows_columns, types = nil) + def create_table(table_data, rows_columns, rows_indexes, types = nil) keyspace_name = table_data['keyspace_name'] table_name = table_data['table_name'] table_flags = table_data['flags'] @@ -1254,6 +1324,10 @@ def create_table(table_data, rows_columns, types = nil) table_options = create_table_options(table_data, compaction_strategy, is_compact) + indexes = rows_indexes.map do |row| + create_index(row) + end + Cassandra::Table.new(@schema.keyspace(keyspace_name), table_name, partition_key, @@ -1261,7 +1335,15 @@ def create_table(table_data, rows_columns, types = nil) other_columns, table_options, clustering_order, - table_data['id']) + table_data['id'], + indexes) + end + + def create_index(row_index) + options = row_index['options'] + Cassandra::Index.new(row_index['index_name'], + row_index['kind'].downcase.to_sym, + options['target'], options) end def create_materialized_view(view_data, rows_columns, base_table, types = nil) diff --git a/lib/cassandra/column.rb b/lib/cassandra/column.rb index b62f1fe5b..2b1e3b8d7 100644 --- a/lib/cassandra/column.rb +++ b/lib/cassandra/column.rb @@ -21,36 +21,18 @@ module Cassandra # @see Cassandra::Table#each_column # @see Cassandra::Table#column class Column - # @private - class Index - # @return [String] index name - attr_reader :name - # @return [String] custom index class name - attr_reader :custom_class_name - - # @private - def initialize(name, custom_class_name = nil) - @name = name - @custom_class_name = custom_class_name - end - end - # @return [String] column name attr_reader :name # @return [Cassandra::Type] column type attr_reader :type # @return [Symbol] column order (`:asc` or `:desc`) attr_reader :order - # @private - # @return [Cassandra::Column::Index, nil] column index - attr_reader :index # @private - def initialize(name, type, order, index = nil, is_static = false, is_frozen = false) + def initialize(name, type, order, is_static = false, is_frozen = false) @name = name @type = type @order = order - @index = index @static = is_static @frozen = is_frozen end @@ -76,7 +58,6 @@ def eql?(other) @name == other.name && @type == other.type && @order == other.order && - @index == other.index && @static == other.static? && @frozen == other.frozen? end diff --git a/lib/cassandra/column_container.rb b/lib/cassandra/column_container.rb index cd38abee7..8ab3c4092 100644 --- a/lib/cassandra/column_container.rb +++ b/lib/cassandra/column_container.rb @@ -305,13 +305,13 @@ def each_column(&block) alias columns each_column # @private - # keyspace may be nil because when this object was constructed, we didn't have - # its keyspace constructed yet. So allow updating keyspace if it's nil, thus + # keyspace attribute may be nil because when this object was constructed, we didn't have + # its keyspace constructed yet. So allow updating @keyspace if it's nil, thus # allowing fetchers to create keyspace, table/view, and hook them together without # worrying about chickens and eggs. - # NOTE: Ignore the set request if the keyspace attribute is already set. + # NOTE: Ignore the set request if the @keyspace is already set. def set_keyspace(keyspace) - @keyspace = keyspace.freeze unless @keyspace + @keyspace = keyspace unless @keyspace end # @private diff --git a/lib/cassandra/index.rb b/lib/cassandra/index.rb new file mode 100644 index 000000000..4a511ca4c --- /dev/null +++ b/lib/cassandra/index.rb @@ -0,0 +1,111 @@ +# encoding: utf-8 + +#-- +# Copyright 2013-2016 DataStax, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + +module Cassandra + # Represents an index on a cassandra table + class Index + # @return [Cassandra::Table] table that the index applies to. + attr_reader :table + # @return [String] name of the index. + attr_reader :name + # @return [Symbol] kind of index: `:keys`, `:composites`, or `:custom`. + attr_reader :kind + # @return [String] name of column that the index applies to. + attr_reader :target + # @return [Hash] options of the index. + attr_reader :options + + # @private + def initialize(name, + kind, + target, + options) + @name = name.freeze + @kind = kind + @target = target.freeze + @options = options.freeze + end + + # @return [Boolean] whether or not this index uses a custom class. + def custom_index? + !@options['class_name'].nil? + end + + # @return [String] name of the index class if this is a custom index; nil otherwise. + def custom_class_name + @options['class_name'] + end + + # @return [String] a cql representation of this table + def to_cql + keyspace_name = Util.escape_name(@table.keyspace.name) + table_name = Util.escape_name(@table.name) + index_name = Util.escape_name(name) + + if custom_index? + "CREATE CUSTOM INDEX #{index_name} ON #{keyspace_name}.#{table_name} (#{target}) " \ + "USING '#{@options['class_name']}' #{options_cql};" + else + "CREATE INDEX #{index_name} ON #{keyspace_name}.#{table_name} (#{target});" + end + end + + # @private + # table attribute is nil when this object is constructed because its table isn't constructed + # yet. So allow updating @table if it's nil, thus allowing fetchers to create + # table and index and then hook them together without worrying about chickens and eggs. + # NOTE: Ignore the set request if @table is already set. + def set_table(table) + @table = table unless @table + end + + # @private + def eql?(other) + other.is_a?(Index) && + @table == other.table && + @name == other.name && + @kind == other.kind && + @target == other.target && + @options == other.options + end + alias == eql? + + # @private + def inspect + "#<#{self.class.name}:0x#{object_id.to_s(16)} " \ + "@name=#{@name} table_name=#{@table.name} kind=#{@kind} target=#{@target}>" + end + + private + + def options_cql + # exclude 'class_name', 'target' keys + filtered_options = @options.reject do |key, _| + key == 'class_name' || key == 'target' + end + return '' if filtered_options.empty? + + result = 'WITH OPTIONS = {' + result << filtered_options.map do |key, value| + "'#{key}':'#{value}'" + end.join(', ') + result << '}' + result + end + end +end diff --git a/lib/cassandra/table.rb b/lib/cassandra/table.rb index 68d395db8..aafa4b60e 100644 --- a/lib/cassandra/table.rb +++ b/lib/cassandra/table.rb @@ -33,10 +33,44 @@ def initialize(keyspace, other_columns, options, clustering_order, - id) + id, + indexes) super(keyspace, name, partition_key, clustering_columns, other_columns, options, id) - @clustering_order = clustering_order + @clustering_order = clustering_order.freeze + @indexes = indexes.freeze + @indexes_hash = @indexes.each_with_object({}) do |ind, h| + h[ind.name] = ind + ind.set_table(self) + end + end + + # @param name [String] index name + # @return [Boolean] whether this table has a given index + def has_index?(name) + @indexes_hash.key?(name) + end + + # @param name [String] index name + # @return [Cassandra::Index, nil] an index or nil + def index(name) + @indexes_hash[name] + end + + # Yield or enumerate each index bound to this table + # @overload each_index + # @yieldparam index [Cassandra::Index] current index + # @return [Cassandra::Table] self + # @overload each_index + # @return [Array] a list of indexes + def each_index(&block) + if block_given? + @indexes.each(&block) + self + else + @indexes + end end + alias indexes each_index # @return [String] a cql representation of this table def to_cql @@ -105,7 +139,8 @@ def to_cql def eql?(other) other.is_a?(Table) && super.eql?(other) && - @clustering_order == other.clustering_order + @clustering_order == other.clustering_order && + @indexes == other.indexes end alias == eql? From c78adae44ec7f953a2a515cb4ed42d0681a86f50 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Wed, 23 Mar 2016 16:52:53 -0700 Subject: [PATCH 013/196] RUBY-178 - Add support for C* index metadata * Rejiggered Table/Index binding. * Fixed cucumber tests to account for different quoting of cql for different versions of C*. --- features/basics/schema_metadata.feature | 25 +++++++++ lib/cassandra/cluster/schema/fetchers.rb | 65 ++++++++++++++---------- lib/cassandra/index.rb | 13 ++--- lib/cassandra/table.rb | 18 ++++--- 4 files changed, 75 insertions(+), 46 deletions(-) diff --git a/features/basics/schema_metadata.feature b/features/basics/schema_metadata.feature index 75f45baad..39fd61eaf 100644 --- a/features/basics/schema_metadata.feature +++ b/features/basics/schema_metadata.feature @@ -46,7 +46,32 @@ Feature: Schema Metadata ) """ + @cassandra-version-specific @cassandra-version-less-3.0 Scenario: Getting index metadata + Given the following schema: + """cql + CREATE KEYSPACE simplex WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 3}; + CREATE TABLE simplex.test_table (f1 int primary key, f2 int); + CREATE INDEX ind1 ON simplex.test_table (f2); + """ + And the following example: + """ruby + require 'cassandra' + + cluster = Cassandra.cluster + + cluster.keyspace('simplex').table('test_table').each_index do |index| + puts index.to_cql + end + """ + When it is executed + Then its output should contain: + """cql + CREATE INDEX "ind1" ON simplex.test_table ("f2"); + """ + + @cassandra-version-specific @cassandra-version-3.0 + Scenario: Getting index metadata on 3.0 Given the following schema: """cql CREATE KEYSPACE simplex WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 3}; diff --git a/lib/cassandra/cluster/schema/fetchers.rb b/lib/cassandra/cluster/schema/fetchers.rb index c65f82284..746c7e55b 100644 --- a/lib/cassandra/cluster/schema/fetchers.rb +++ b/lib/cassandra/cluster/schema/fetchers.rb @@ -461,29 +461,32 @@ def create_table(table_data, rows_columns, rows_indexes) end end - # In C* 1.2.x, index info is in the column metadata; rows_indexes is nil. - indexes = [] + index_rows = [] rows_columns.each do |row| column = create_column(row) other_columns << column - unless row['index_type'].nil? - indexes << create_index(column, row) - end + # In C* 1.2.x, index info is in the column metadata; rows_indexes is nil. + index_rows << [column, row] unless row['index_type'].nil? end - Cassandra::Table.new(@schema.keyspace(keyspace_name), + table = Cassandra::Table.new(@schema.keyspace(keyspace_name), table_name, partition_key, clustering_columns, other_columns, table_options, clustering_order, - table_data['id'], - indexes) + table_data['id']) + + # Create Index objects and add them to the table. + index_rows.each do |column, row| + create_index(table, column, row) + end + table end - def create_index(column, row_column) + def create_index(table, column, row_column) # Most of this logic was taken from the Java driver. options = {} # For some versions of C*, this field could have a literal string 'null' value. @@ -503,7 +506,11 @@ def create_index(column, row_column) column_name end - Cassandra::Index.new(row_column['index_name'], row_column['index_type'].downcase.to_sym, target, options) + table.add_index(Cassandra::Index.new(table, + row_column['index_name'], + row_column['index_type'].downcase.to_sym, + target, + options)) end def create_column(column_data) @@ -581,8 +588,7 @@ def create_table(table_data, rows_columns, rows_indexes) clustering_order = [] other_columns = [] - # In C* 2.0.x, index info is in the column metadata; rows_indexes is nil. - indexes = [] + index_rows = [] rows_columns.each do |row| next if row['column_name'].empty? @@ -604,9 +610,8 @@ def create_table(table_data, rows_columns, rows_indexes) other_columns << column end - unless row['index_type'].nil? - indexes << create_index(column, row) - end + # In C* 2.0.x, index info is in the column metadata; rows_indexes is nil. + index_rows << [column, row] unless row['index_type'].nil? end compaction_strategy = create_compaction_strategy(table_data) @@ -615,15 +620,20 @@ def create_table(table_data, rows_columns, rows_indexes) table_options = create_table_options(table_data, compaction_strategy, is_compact) - Cassandra::Table.new(@schema.keyspace(keyspace_name), + table = Cassandra::Table.new(@schema.keyspace(keyspace_name), table_name, partition_key, clustering_columns, other_columns, table_options, clustering_order, - table_data['id'], - indexes) + table_data['id']) + + # Create Index objects and add them to the table. + index_rows.each do |column, row| + create_index(table, column, row) + end + table end def select_keyspace(connection, keyspace_name) @@ -1324,26 +1334,25 @@ def create_table(table_data, rows_columns, rows_indexes, types = nil) table_options = create_table_options(table_data, compaction_strategy, is_compact) - indexes = rows_indexes.map do |row| - create_index(row) - end - - Cassandra::Table.new(@schema.keyspace(keyspace_name), + table = Cassandra::Table.new(@schema.keyspace(keyspace_name), table_name, partition_key, clustering_columns, other_columns, table_options, clustering_order, - table_data['id'], - indexes) + table_data['id']) + rows_indexes.each do |row| + create_index(table, row) + end + table end - def create_index(row_index) + def create_index(table, row_index) options = row_index['options'] - Cassandra::Index.new(row_index['index_name'], + table.add_index(Cassandra::Index.new(table, row_index['index_name'], row_index['kind'].downcase.to_sym, - options['target'], options) + options['target'], options)) end def create_materialized_view(view_data, rows_columns, base_table, types = nil) diff --git a/lib/cassandra/index.rb b/lib/cassandra/index.rb index 4a511ca4c..856137594 100644 --- a/lib/cassandra/index.rb +++ b/lib/cassandra/index.rb @@ -31,10 +31,12 @@ class Index attr_reader :options # @private - def initialize(name, + def initialize(table, + name, kind, target, options) + @table = table @name = name.freeze @kind = kind @target = target.freeze @@ -65,15 +67,6 @@ def to_cql end end - # @private - # table attribute is nil when this object is constructed because its table isn't constructed - # yet. So allow updating @table if it's nil, thus allowing fetchers to create - # table and index and then hook them together without worrying about chickens and eggs. - # NOTE: Ignore the set request if @table is already set. - def set_table(table) - @table = table unless @table - end - # @private def eql?(other) other.is_a?(Index) && diff --git a/lib/cassandra/table.rb b/lib/cassandra/table.rb index aafa4b60e..a58d8a138 100644 --- a/lib/cassandra/table.rb +++ b/lib/cassandra/table.rb @@ -33,15 +33,11 @@ def initialize(keyspace, other_columns, options, clustering_order, - id, - indexes) + id) super(keyspace, name, partition_key, clustering_columns, other_columns, options, id) @clustering_order = clustering_order.freeze - @indexes = indexes.freeze - @indexes_hash = @indexes.each_with_object({}) do |ind, h| - h[ind.name] = ind - ind.set_table(self) - end + @indexes = [] + @indexes_hash = {} end # @param name [String] index name @@ -67,7 +63,7 @@ def each_index(&block) @indexes.each(&block) self else - @indexes + @indexes.freeze end end alias indexes each_index @@ -135,6 +131,12 @@ def to_cql cql << ';' end + # @private + def add_index(index) + @indexes << index + @indexes_hash[index.name] = index + end + # @private def eql?(other) other.is_a?(Table) && From bf33e04b44e78e1152204108b96471342e74ccaa Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Thu, 24 Mar 2016 15:12:19 -0700 Subject: [PATCH 014/196] * Ran rubocop on the source tree and corrected all violations * Added an attr_boolean helper method to define trivial boolean getters. --- .rubocop.yml | 16 +++- .rubocop_todo.yml | 23 ------ lib/cassandra.rb | 25 +++--- lib/cassandra/attr_boolean.rb | 32 ++++++++ lib/cassandra/auth.rb | 3 +- lib/cassandra/auth/providers/password.rb | 6 -- lib/cassandra/cluster/connector.rb | 2 +- lib/cassandra/cluster/control_connection.rb | 76 ++++++------------- lib/cassandra/cluster/metadata.rb | 4 +- lib/cassandra/cluster/options.rb | 17 +---- lib/cassandra/cluster/registry.rb | 2 +- .../cluster/schema/fqcn_type_parser.rb | 8 +- .../cluster/schema/partitioners/murmur3.rb | 12 ++- lib/cassandra/column_container.rb | 48 +++++------- lib/cassandra/compression/compressors/lz4.rb | 8 +- lib/cassandra/errors.rb | 54 ++++++++----- lib/cassandra/execution/options.rb | 6 +- lib/cassandra/future.rb | 12 +-- lib/cassandra/host.rb | 4 +- lib/cassandra/index.rb | 8 +- lib/cassandra/keyspace.rb | 2 +- .../policies/dc_aware_round_robin.rb | 16 ++-- lib/cassandra/materialized_view.rb | 27 ++++--- lib/cassandra/protocol/coder.rb | 6 +- lib/cassandra/protocol/cql_byte_buffer.rb | 23 +++--- .../protocol/cql_protocol_handler.rb | 4 +- .../protocol/requests/execute_request.rb | 8 +- .../protocol/requests/query_request.rb | 2 +- .../protocol/requests/startup_request.rb | 14 ++-- lib/cassandra/protocol/response.rb | 3 +- .../responses/auth_challenge_response.rb | 7 +- .../responses/auth_success_response.rb | 7 +- .../responses/authenticate_response.rb | 7 +- .../protocol/responses/error_response.rb | 7 +- .../protocol/responses/event_response.rb | 5 +- .../responses/prepared_result_response.rb | 7 +- .../protocol/responses/ready_response.rb | 7 +- .../protocol/responses/result_response.rb | 15 ++-- .../responses/rows_result_response.rb | 7 +- .../responses/schema_change_event_response.rb | 7 +- .../schema_change_result_response.rb | 7 +- .../responses/set_keyspace_result_response.rb | 7 +- .../responses/status_change_event_response.rb | 7 +- .../protocol/responses/supported_response.rb | 7 +- .../topology_change_event_response.rb | 7 +- .../responses/void_result_response.rb | 7 +- lib/cassandra/protocol/v1.rb | 6 +- lib/cassandra/protocol/v3.rb | 4 +- lib/cassandra/result.rb | 3 +- .../retry/policies/downgrading_consistency.rb | 4 +- lib/cassandra/statements/prepared.rb | 6 +- lib/cassandra/table.rb | 6 +- lib/cassandra/time_uuid.rb | 12 ++- lib/cassandra/tuple.rb | 16 +--- lib/cassandra/udt.rb | 8 +- lib/cassandra/uuid.rb | 28 +++---- 56 files changed, 290 insertions(+), 392 deletions(-) create mode 100644 lib/cassandra/attr_boolean.rb diff --git a/.rubocop.yml b/.rubocop.yml index 29bad472e..59b887c19 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -28,23 +28,33 @@ Metrics/ClassLength: Style/MultilineTernaryOperator: Enabled: false +Style/PredicateName: + NamePrefixBlacklist: is_, have_ + NameWhitelist: is_a? + Style/SingleLineBlockParams: Enabled: false Style/SignalException: EnforcedStyle: only_raise +Style/TrivialAccessors: + Enabled: false + Lint/EndAlignment: AutoCorrect: true Style/IfUnlessModifier: - MaxLineLength: 90 + MaxLineLength: 120 + +Style/RaiseArgs: + Enabled: false Style/WhileUntilModifier: - MaxLineLength: 90 + MaxLineLength: 120 Metrics/LineLength: - Max: 90 + Max: 120 # To make it possible to copy or click on URIs in the code, we allow lines # contaning a URI to be longer than Max. AllowHeredoc: true diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 81ee43c38..a89afb42a 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -190,29 +190,6 @@ Style/OpMethod: - 'lib/cassandra/protocol/requests/query_request.rb' - 'lib/cassandra/protocol/responses/ready_response.rb' -# Offense count: 14 -# Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist. -# NamePrefix: is_, has_, have_ -# NamePrefixBlacklist: is_, has_, have_ -# NameWhitelist: is_a? -Style/PredicateName: - Exclude: - - 'lib/cassandra/cluster/registry.rb' - - 'lib/cassandra/cluster/schema.rb' - - 'lib/cassandra/function.rb' - - 'lib/cassandra/keyspace.rb' - - 'lib/cassandra/load_balancing.rb' - - 'lib/cassandra/load_balancing/policies/dc_aware_round_robin.rb' - - 'lib/cassandra/load_balancing/policies/round_robin.rb' - - 'lib/cassandra/load_balancing/policies/token_aware.rb' - - 'lib/cassandra/table.rb' - - 'lib/cassandra/udt.rb' - -# Offense count: 2 -# Configuration parameters: EnforcedStyle, SupportedStyles. -# SupportedStyles: compact, exploded -#Style/RaiseArgs: -# Enabled: false # Offense count: 46 # Cop supports --auto-correct. diff --git a/lib/cassandra.rb b/lib/cassandra.rb index b0a5059fc..0e31a4387 100644 --- a/lib/cassandra.rb +++ b/lib/cassandra.rb @@ -34,8 +34,10 @@ module Cassandra # A list of all supported request consistencies - # @see http://www.datastax.com/documentation/cassandra/2.0/cassandra/dml/dml_config_consistency_c.html Consistency levels in Apache Cassandra 2.0 - # @see http://www.datastax.com/documentation/cassandra/1.2/cassandra/dml/dml_config_consistency_c.html Consistency levels in Apache Cassandra 1.2 + # @see http://www.datastax.com/documentation/cassandra/2.0/cassandra/dml/dml_config_consistency_c.html Consistency + # levels in Apache Cassandra 2.0 + # @see http://www.datastax.com/documentation/cassandra/1.2/cassandra/dml/dml_config_consistency_c.html Consistency + # levels in Apache Cassandra 1.2 # @see Cassandra::Session#execute_async CONSISTENCIES = [:any, :one, :two, :three, :quorum, :all, :local_quorum, :each_quorum, :serial, :local_serial, :local_one].freeze @@ -47,7 +49,8 @@ module Cassandra # A list of all possible write types that a # {Cassandra::Errors::WriteTimeoutError} can have. # - # @see https://github.com/apache/cassandra/blob/cassandra-2.0.16/doc/native_protocol_v2.spec#L872-L887 Description of possible types of writes in Apache Cassandra native protocol spec v1 + # @see https://github.com/apache/cassandra/blob/cassandra-2.0.16/doc/native_protocol_v2.spec#L872-L887 Description of + # possible types of writes in Apache Cassandra native protocol spec v1 WRITE_TYPES = [:simple, :batch, :unlogged_batch, :counter, :batch_log].freeze CLUSTER_OPTIONS = [ @@ -615,12 +618,8 @@ def self.validate_and_massage_options(options) end options[:nodelay] = !!options[:nodelay] if options.key?(:nodelay) - options[:trace] = !!options[:trace] if options.key?(:trace) - - if options.key?(:shuffle_replicas) - options[:shuffle_replicas] = !!options[:shuffle_replicas] - end + options[:shuffle_replicas] = !!options[:shuffle_replicas] if options.key?(:shuffle_replicas) if options.key?(:page_size) page_size = options[:page_size] @@ -669,13 +668,8 @@ def self.validate_and_massage_options(options) end end - if options.key?(:synchronize_schema) - options[:synchronize_schema] = !!options[:synchronize_schema] - end - - if options.key?(:client_timestamps) - options[:client_timestamps] = !!options[:client_timestamps] - end + options[:synchronize_schema] = !!options[:synchronize_schema] if options.key?(:synchronize_schema) + options[:client_timestamps] = !!options[:client_timestamps] if options.key?(:client_timestamps) if options.key?(:connections_per_local_node) connections_per_node = options[:connections_per_local_node] @@ -740,6 +734,7 @@ def self.validate_and_massage_options(options) DATE_OFFSET = (::Time.utc(1970, 1, 1).to_date.jd - 2**31) end +require 'cassandra/attr_boolean' require 'cassandra/version' require 'cassandra/uuid' require 'cassandra/time_uuid' diff --git a/lib/cassandra/attr_boolean.rb b/lib/cassandra/attr_boolean.rb new file mode 100644 index 000000000..cefcb6590 --- /dev/null +++ b/lib/cassandra/attr_boolean.rb @@ -0,0 +1,32 @@ +# encoding: utf-8 + +#-- +# Copyright 2013-2016 DataStax, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + +# This file monkey-patches Module to have an attr_boolean method to make it easy +# for classes to define boolean instance variables with "foo?" reader methods. +# Inspired by http://stackoverflow.com/questions/4013591/attr-reader-with-question-mark-in-a-name + +class Module + def attr_boolean(*names) + names.each do |name| + define_method(:"#{name}?") do + res = instance_variable_get(:"@#{name}") + !res.nil? && res + end + end + end +end diff --git a/lib/cassandra/auth.rb b/lib/cassandra/auth.rb index 6d40f6680..803640e14 100644 --- a/lib/cassandra/auth.rb +++ b/lib/cassandra/auth.rb @@ -59,7 +59,8 @@ class Provider # subclasses of this class, but need to implement the same methods. This # class exists only for documentation purposes. # - # @see https://github.com/apache/cassandra/blob/cassandra-2.0.16/doc/native_protocol_v2.spec#L257-L273 Cassandra native protocol v2 SASL authentication + # @see https://github.com/apache/cassandra/blob/cassandra-2.0.16/doc/native_protocol_v2.spec#L257-L273 Cassandra + # native protocol v2 SASL authentication # @see Cassandra::Auth::Provider#create_authenticator class Authenticator # @!method initial_response diff --git a/lib/cassandra/auth/providers/password.rb b/lib/cassandra/auth/providers/password.rb index 1128d4e10..d1f6030a5 100644 --- a/lib/cassandra/auth/providers/password.rb +++ b/lib/cassandra/auth/providers/password.rb @@ -59,12 +59,6 @@ def initialize(username, password) def create_authenticator(authentication_class) Authenticator.new(@username, @password) end - - private - - # @private - PASSWORD_AUTHENTICATOR_FQCN = - 'org.apache.cassandra.auth.PasswordAuthenticator'.freeze end end end diff --git a/lib/cassandra/cluster/connector.rb b/lib/cassandra/cluster/connector.rb index da7ee7425..f37118dbd 100644 --- a/lib/cassandra/cluster/connector.rb +++ b/lib/cassandra/cluster/connector.rb @@ -172,7 +172,7 @@ def do_connect(host) # don't want to accidentally raise it, so we update it to the min # of itself and current-1. @connection_options.protocol_version = - [@connection_options.protocol_version, current_version - 1].min + [@connection_options.protocol_version, current_version - 1].min do_connect(host) else Ione::Future.failed(error) diff --git a/lib/cassandra/cluster/control_connection.rb b/lib/cassandra/cluster/control_connection.rb index dd7361754..fcbed87bc 100644 --- a/lib/cassandra/cluster/control_connection.rb +++ b/lib/cassandra/cluster/control_connection.rb @@ -191,18 +191,14 @@ def reconnect_async(schedule) def register_async connection = @connection - if connection.nil? - return Ione::Future.failed(Errors::ClientError.new('Not connected')) - end + return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil? request = Protocol::RegisterRequest.new( Protocol::TopologyChangeEventResponse::TYPE, Protocol::StatusChangeEventResponse::TYPE ) - if @connection_options.synchronize_schema? - request.events << Protocol::SchemaChangeEventResponse::TYPE - end + request.events << Protocol::SchemaChangeEventResponse::TYPE if @connection_options.synchronize_schema? f = connection.send_request(request) f = f.map do |r| @@ -257,9 +253,7 @@ def refresh_schema_async @logger.info('Refreshing schema') - if connection.nil? - return Ione::Future.failed(Errors::ClientError.new('Not connected')) - end + return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil? @schema_fetcher.fetch(connection).map do |keyspaces| @schema.replace(keyspaces) @@ -271,9 +265,7 @@ def refresh_schema_async def refresh_keyspace_async(keyspace_name) connection = @connection - if connection.nil? - return Ione::Future.failed(Errors::ClientError.new('Not connected')) - end + return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil? @logger.info("Refreshing keyspace \"#{keyspace_name}\"") @@ -291,9 +283,7 @@ def refresh_keyspace_async(keyspace_name) def refresh_table_async(keyspace_name, table_name) connection = @connection - if connection.nil? - return Ione::Future.failed(Errors::ClientError.new('Not connected')) - end + return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil? @logger.info("Refreshing table \"#{keyspace_name}.#{table_name}\"") @@ -311,9 +301,7 @@ def refresh_table_async(keyspace_name, table_name) def refresh_materialized_view_async(keyspace_name, view_name) connection = @connection - if connection.nil? - return Ione::Future.failed(Errors::ClientError.new('Not connected')) - end + return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil? @logger.info("Refreshing materialized view \"#{keyspace_name}.#{view_name}\"") @@ -331,9 +319,7 @@ def refresh_materialized_view_async(keyspace_name, view_name) def refresh_type_async(keyspace_name, type_name) connection = @connection - if connection.nil? - return Ione::Future.failed(Errors::ClientError.new('Not connected')) - end + return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil? @logger.info("Refreshing user-defined type \"#{keyspace_name}.#{type_name}\"") @@ -351,9 +337,7 @@ def refresh_type_async(keyspace_name, type_name) def refresh_function_async(keyspace_name, function_name, function_args) connection = @connection - if connection.nil? - return Ione::Future.failed(Errors::ClientError.new('Not connected')) - end + return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil? @logger.info('Refreshing user-defined function ' \ "\"#{keyspace_name}.#{function_name}\"") @@ -379,9 +363,7 @@ def refresh_function_async(keyspace_name, function_name, function_args) def refresh_aggregate_async(keyspace_name, aggregate_name, aggregate_args) connection = @connection - if connection.nil? - return Ione::Future.failed(Errors::ClientError.new('Not connected')) - end + return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil? @logger.info('Refreshing user-defined aggregate ' \ "\"#{keyspace_name}.#{aggregate_name}\"") @@ -452,9 +434,7 @@ def refresh_peers_async_retry(error, schedule) def refresh_peers_async connection = @connection - if connection.nil? - return Ione::Future.failed(Errors::ClientError.new('Not connected')) - end + return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil? @logger.info('Refreshing peers metadata') @@ -485,20 +465,16 @@ def refresh_peers_async def refresh_metadata_async connection = @connection - if connection.nil? - return Ione::Future.failed(Errors::ClientError.new('Not connected')) - end + return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil? @logger.info("Refreshing connected host's metadata") send_select_request(connection, SELECT_LOCAL).map do |local| - if local.empty? - raise Errors::InternalError, "Unable to fetch connected host's metadata" - else - data = local.first - @registry.host_found(IPAddr.new(connection.host), data) - @metadata.update(data) - end + raise Errors::InternalError, "Unable to fetch connected host's metadata" if local.empty? + + data = local.first + @registry.host_found(IPAddr.new(connection.host), data) + @metadata.update(data) @logger.info("Completed refreshing connected host's metadata") @@ -579,9 +555,7 @@ def refresh_host_async_retry(address, error, schedule) def refresh_host_async(address) connection = @connection - if connection.nil? - return Ione::Future.failed(Errors::ClientError.new('Not connected')) - end + return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil? ip = address.to_s @@ -597,12 +571,10 @@ def refresh_host_async(address) end send_select_request(connection, request).map do |rows| - if rows.empty? - raise Errors::InternalError, "Unable to find host metadata: #{ip}" - else - @logger.info("Completed refreshing host metadata: #{ip}") - @registry.host_found(address, rows.first) - end + raise Errors::InternalError, "Unable to find host metadata: #{ip}" if rows.empty? + + @logger.info("Completed refreshing host metadata: #{ip}") + @registry.host_found(address, rows.first) self end @@ -667,9 +639,7 @@ def connect_to_first_available(plan, errors = nil) end f = f.flat_map { register_async } f = f.flat_map { refresh_peers_async_maybe_retry } - if @connection_options.synchronize_schema? - f = f.flat_map { refresh_maybe_retry(:schema) } - end + f = f.flat_map { refresh_maybe_retry(:schema) } if @connection_options.synchronize_schema? f = f.fallback do |error| @logger.debug("Connection to #{host.ip} failed " \ "(#{error.class.name}: #{error.message})") @@ -703,7 +673,7 @@ def peer_ip(data) end def process_schema_changes(schema_changes) - refresh_keyspaces = ::Hash.new + refresh_keyspaces = ::Hash.new refresh_tables_and_views = ::Hash.new refresh_types = ::Hash.new diff --git a/lib/cassandra/cluster/metadata.rb b/lib/cassandra/cluster/metadata.rb index 230ed5384..8db783583 100644 --- a/lib/cassandra/cluster/metadata.rb +++ b/lib/cassandra/cluster/metadata.rb @@ -39,9 +39,7 @@ def initialize(cluster_registry, end def find_replicas(keyspace, statement) - unless statement.respond_to?(:partition_key) && statement.respond_to?(:keyspace) - return EMPTY_LIST - end + return EMPTY_LIST unless statement.respond_to?(:partition_key) && statement.respond_to?(:keyspace) keyspace = String(statement.keyspace || keyspace) partition_key = statement.partition_key diff --git a/lib/cassandra/cluster/options.rb b/lib/cassandra/cluster/options.rb index 3f4b422a0..cf57f9c8f 100644 --- a/lib/cassandra/cluster/options.rb +++ b/lib/cassandra/cluster/options.rb @@ -23,6 +23,7 @@ class Options attr_reader :auth_provider, :compressor, :connect_timeout, :credentials, :heartbeat_interval, :idle_timeout, :port, :schema_refresh_delay, :schema_refresh_timeout, :ssl + attr_boolean :protocol_negotiable, :synchronize_schema, :client_timestamps, :nodelay attr_accessor :protocol_version @@ -73,22 +74,6 @@ def initialize(logger, @protocol_version ||= 4 end - def protocol_negotiable? - @protocol_negotiable - end - - def synchronize_schema? - @synchronize_schema - end - - def client_timestamps? - @client_timestamps - end - - def nodelay? - @nodelay - end - def compression @compressor && @compressor.algorithm end diff --git a/lib/cassandra/cluster/registry.rb b/lib/cassandra/cluster/registry.rb index 1c6f15a7e..41962587f 100644 --- a/lib/cassandra/cluster/registry.rb +++ b/lib/cassandra/cluster/registry.rb @@ -171,7 +171,7 @@ def create_host(ip, data) :up, data['broadcast_address'], data['listen_address'] - ) + ) end def toggle_up(host) diff --git a/lib/cassandra/cluster/schema/fqcn_type_parser.rb b/lib/cassandra/cluster/schema/fqcn_type_parser.rb index 9472c12b0..5cd900529 100644 --- a/lib/cassandra/cluster/schema/fqcn_type_parser.rb +++ b/lib/cassandra/cluster/schema/fqcn_type_parser.rb @@ -109,9 +109,7 @@ def create_type(node) end def lookup_type(node) - if node.name == 'org.apache.cassandra.db.marshal.FrozenType' - return lookup_type(node.children.first) - end + return lookup_type(node.children.first) if node.name == 'org.apache.cassandra.db.marshal.FrozenType' type = @@types.fetch(node.name) do return Cassandra::Types.custom(dump_node(node)) @@ -169,9 +167,7 @@ def parse_node(string) def dump_node(node) str = node.name - unless node.children.empty? - str << '(' + node.children.map { |n| dump_node(n) }.join(',') + ')' - end + str << '(' + node.children.map { |n| dump_node(n) }.join(',') + ')' unless node.children.empty? str end end diff --git a/lib/cassandra/cluster/schema/partitioners/murmur3.rb b/lib/cassandra/cluster/schema/partitioners/murmur3.rb index 6d7ebb783..f3de1bb0e 100644 --- a/lib/cassandra/cluster/schema/partitioners/murmur3.rb +++ b/lib/cassandra/cluster/schema/partitioners/murmur3.rb @@ -23,6 +23,11 @@ class Schema module Partitioners # @private class Murmur3 + # @private + LONG_MIN = -2**63 + # @private + LONG_MAX = 2**63 - 1 + def create_token(partition_key) token = Cassandra::Murmur3.hash(partition_key) token = LONG_MAX if token == LONG_MIN @@ -33,13 +38,6 @@ def create_token(partition_key) def parse_token(token_string) token_string.to_i end - - private - - # @private - LONG_MIN = -2**63 - # @private - LONG_MAX = 2**63 - 1 end end end diff --git a/lib/cassandra/column_container.rb b/lib/cassandra/column_container.rb index 8ab3c4092..c02dd9bbb 100644 --- a/lib/cassandra/column_container.rb +++ b/lib/cassandra/column_container.rb @@ -44,7 +44,8 @@ class Options # exceeded the Nth percentile of read response times for this object" (e.g. 99percentile). # @return [String] the speculative retry setting of this column-container. attr_reader :speculative_retry - # Return the index interval of this column-container; Cassandra will hold `1/index_interval` of row keys in memory. + # Return the index interval of this column-container; Cassandra will hold `1/index_interval` of row keys in + # memory. # @return [Integer] the index interval of this column-container. May be nil, indicating a default value of 128. attr_reader :index_interval # @return [Hash] compression settings @@ -60,6 +61,7 @@ class Options attr_reader :compaction_strategy # @private + # rubocop:disable Metrics/ParameterLists def initialize(comment, read_repair_chance, local_read_repair_chance, @@ -130,12 +132,8 @@ def to_cql end options << "caching = #{Util.encode_object(@caching)}" unless @caching.nil? options << "comment = #{Util.encode_object(@comment)}" unless @comment.nil? - unless @compaction_strategy.nil? - options << "compaction = #{@compaction_strategy.to_cql}" - end - unless @compression.nil? - options << "compression = #{Util.encode_object(@compression)}" - end + options << "compaction = #{@compaction_strategy.to_cql}" unless @compaction_strategy.nil? + options << "compression = #{Util.encode_object(@compression)}" unless @compression.nil? unless @local_read_repair_chance.nil? options << 'dclocal_read_repair_chance = ' \ "#{Util.encode_object(@local_read_repair_chance)}" @@ -143,34 +141,20 @@ def to_cql unless @default_time_to_live.nil? options << "default_time_to_live = #{Util.encode_object(@default_time_to_live)}" end - unless @gc_grace_seconds.nil? - options << "gc_grace_seconds = #{Util.encode_object(@gc_grace_seconds)}" - end - unless @index_interval.nil? - options << "index_interval = #{Util.encode_object(@index_interval)}" - end - unless @max_index_interval.nil? - options << "max_index_interval = #{Util.encode_object(@max_index_interval)}" - end + options << "gc_grace_seconds = #{Util.encode_object(@gc_grace_seconds)}" unless @gc_grace_seconds.nil? + options << "index_interval = #{Util.encode_object(@index_interval)}" unless @index_interval.nil? + options << "max_index_interval = #{Util.encode_object(@max_index_interval)}" unless @max_index_interval.nil? unless @memtable_flush_period_in_ms.nil? options << 'memtable_flush_period_in_ms = ' \ "#{Util.encode_object(@memtable_flush_period_in_ms)}" end - unless @min_index_interval.nil? - options << "min_index_interval = #{Util.encode_object(@min_index_interval)}" - end + options << "min_index_interval = #{Util.encode_object(@min_index_interval)}" unless @min_index_interval.nil? unless @populate_io_cache_on_flush.nil? options << "populate_io_cache_on_flush = '#{@populate_io_cache_on_flush}'" end - unless @read_repair_chance.nil? - options << "read_repair_chance = #{Util.encode_object(@read_repair_chance)}" - end - unless @replicate_on_write.nil? - options << "replicate_on_write = '#{@replicate_on_write}'" - end - unless @speculative_retry.nil? - options << "speculative_retry = #{Util.encode_object(@speculative_retry)}" - end + options << "read_repair_chance = #{Util.encode_object(@read_repair_chance)}" unless @read_repair_chance.nil? + options << "replicate_on_write = '#{@replicate_on_write}'" unless @replicate_on_write.nil? + options << "speculative_retry = #{Util.encode_object(@speculative_retry)}" unless @speculative_retry.nil? unless @crc_check_chance.nil? options << 'crc_check_chance = ' \ "#{Util.encode_object(@crc_check_chance)}" @@ -212,7 +196,7 @@ class Compaction # @private def initialize(class_name, options) - @class_name = class_name + @class_name = class_name @options = options end @@ -245,8 +229,10 @@ def eql?(other) attr_reader :partition_key # @return [Array] ordered list of column-names that make up the clustering-columns. attr_reader :clustering_columns - # @return [Array] primary key of this column-container. It's the combination of `partition_key` and `clustering_columns`. - # @note This composition produces a flat list, so it will not be possible for the caller to distinguish partition-key columns from clustering-columns. + # @return [Array] primary key of this column-container. It's the combination of + # `partition_key` and `clustering_columns`. + # @note This composition produces a flat list, so it will not be possible for the caller to distinguish + # partition-key columns from clustering-columns. attr_reader :primary_key # @private diff --git a/lib/cassandra/compression/compressors/lz4.rb b/lib/cassandra/compression/compressors/lz4.rb index 3d67c929a..2a248f31b 100644 --- a/lib/cassandra/compression/compressors/lz4.rb +++ b/lib/cassandra/compression/compressors/lz4.rb @@ -28,6 +28,9 @@ module Compressors # :lz4` option when calling {Cassandra.cluster} and one will be created # automatically for you. class Lz4 < Compressor + # @private + BUFFER_FORMAT = 'Na*'.freeze + # @return [String] `'lz4'` attr_reader :algorithm @@ -64,11 +67,6 @@ def decompress(str) decompressed_size, compressed_data = str.to_s.unpack(BUFFER_FORMAT) ::LZ4::Raw.decompress(compressed_data, decompressed_size).first end - - private - - # @private - BUFFER_FORMAT = 'Na*'.freeze end end end diff --git a/lib/cassandra/errors.rb b/lib/cassandra/errors.rb index efb9a3a54..30402bac2 100644 --- a/lib/cassandra/errors.rb +++ b/lib/cassandra/errors.rb @@ -43,7 +43,8 @@ module HostError # Raised when something unexpected happened. This indicates a server-side # bug. # - # @see https://github.com/apache/cassandra/blob/cassandra-2.0.16/doc/native_protocol_v1.spec#L654-L655 Description of Server Error in Apache Cassandra native protocol spec v1 + # @see https://github.com/apache/cassandra/blob/cassandra-2.0.16/doc/native_protocol_v1.spec#L654-L655 Description + # of Server Error in Apache Cassandra native protocol spec v1 class ServerError < ::StandardError include Error, HostError @@ -155,7 +156,8 @@ def execution_info # @note This error can be handled by a {Cassandra::Retry::Policy} to # determine the desired outcome. # - # @see https://github.com/apache/cassandra/blob/cassandra-2.0.16/doc/native_protocol_v1.spec#L662-L672 Description of Unavailable Error in Apache Cassandra native protocol spec v1 + # @see https://github.com/apache/cassandra/blob/cassandra-2.0.16/doc/native_protocol_v1.spec#L662-L672 Description + # of Unavailable Error in Apache Cassandra native protocol spec v1 class UnavailableError < ::StandardError include ExecutionError # Consistency level that triggered the error. @@ -202,7 +204,8 @@ def initialize(message, # Raised when the request cannot be processed because the coordinator node # is overloaded # - # @see https://github.com/apache/cassandra/blob/cassandra-2.0.16/doc/native_protocol_v1.spec#L673-L674 Description of Overloaded Error in Apache Cassandra native protocol spec v1 + # @see https://github.com/apache/cassandra/blob/cassandra-2.0.16/doc/native_protocol_v1.spec#L673-L674 Description + # of Overloaded Error in Apache Cassandra native protocol spec v1 class OverloadedError < ::StandardError include ExecutionError, HostError end @@ -210,21 +213,24 @@ class OverloadedError < ::StandardError # Raise when the request was a read request but the coordinator node is # bootstrapping # - # @see https://github.com/apache/cassandra/blob/cassandra-2.0.16/doc/native_protocol_v1.spec#L675-L676 Description of Is Bootstrapping Error in Apache Cassandra native protocol spec v1 + # @see https://github.com/apache/cassandra/blob/cassandra-2.0.16/doc/native_protocol_v1.spec#L675-L676 Description + # of Is Bootstrapping Error in Apache Cassandra native protocol spec v1 class IsBootstrappingError < ::StandardError include ExecutionError, HostError end # Raised when truncation failed. # - # @see https://github.com/apache/cassandra/blob/cassandra-2.0.16/doc/native_protocol_v1.spec#L677 Description of Truncate Error in Apache Cassandra native protocol spec v1 + # @see https://github.com/apache/cassandra/blob/cassandra-2.0.16/doc/native_protocol_v1.spec#L677 Description of + # Truncate Error in Apache Cassandra native protocol spec v1 class TruncateError < ::StandardError include ExecutionError end # Raised when a write request timed out. # - # @see https://github.com/apache/cassandra/blob/cassandra-2.0.16/doc/native_protocol_v1.spec#L678-L703 Description of Write Timeout Error in Apache Cassandra native protocol spec v1 + # @see https://github.com/apache/cassandra/blob/cassandra-2.0.16/doc/native_protocol_v1.spec#L678-L703 Description + # of Write Timeout Error in Apache Cassandra native protocol spec v1 class WriteTimeoutError < ::StandardError include ExecutionError @@ -273,7 +279,8 @@ def initialize(message, # Raised when a read request timed out. # - # @see https://github.com/apache/cassandra/blob/cassandra-2.0.16/doc/native_protocol_v1.spec#L704-L721 Description of Read Timeout Error in Apache Cassandra native protocol spec v1 + # @see https://github.com/apache/cassandra/blob/cassandra-2.0.16/doc/native_protocol_v1.spec#L704-L721 Description + # of Read Timeout Error in Apache Cassandra native protocol spec v1 class ReadTimeoutError < ::StandardError include ExecutionError @@ -326,7 +333,8 @@ def retrieved? # Raised when a write request fails. # - # @see https://github.com/apache/cassandra/blob/33f1edcce97779c971d4f78712a9a8bf014ffbbc/doc/native_protocol_v4.spec#L1111-L1138 Description of Write Failure Error in Apache Cassandra native protocol spec v4 + # @see https://github.com/apache/cassandra/blob/cassandra-3.4/doc/native_protocol_v4.spec#L1106-L1134 Description + # of Write Failure Error in Apache Cassandra native protocol spec v4 class WriteError < ::StandardError include ExecutionError @@ -377,7 +385,8 @@ def initialize(message, # Raised when a read request fails. # - # @see https://github.com/apache/cassandra/blob/33f1edcce97779c971d4f78712a9a8bf014ffbbc/doc/native_protocol_v4.spec#L1089-L1103 Description of Read Failure Error in Apache Cassandra native protocol spec v4 + # @see https://github.com/apache/cassandra/blob/cassandra-3.4/doc/native_protocol_v4.spec#L1084-L1098 Description + # of Read Failure Error in Apache Cassandra native protocol spec v4 class ReadError < ::StandardError include ExecutionError @@ -432,7 +441,8 @@ def retrieved? # Raised when function execution fails. # - # @see https://github.com/apache/cassandra/blob/33f1edcce97779c971d4f78712a9a8bf014ffbbc/doc/native_protocol_v4.spec#L1104-L1110 Description of Function Failure Error in Apache Cassandra native protocol spec v4 + # @see https://github.com/apache/cassandra/blob/cassandra-3.4/doc/native_protocol_v4.spec#L1099-L1105 Description + # of Function Failure Error in Apache Cassandra native protocol spec v4 class FunctionCallError < ::StandardError include ExecutionError @@ -479,7 +489,8 @@ class ClientError < ::StandardError # Raised when some client message triggered a protocol violation (for # instance a QUERY message is sent before a STARTUP one has been sent) # - # @see https://github.com/apache/cassandra/blob/cassandra-2.0.16/doc/native_protocol_v1.spec#L656-L658 Description of Protocol Error in Apache Cassandra native protocol spec v1 + # @see https://github.com/apache/cassandra/blob/cassandra-2.0.16/doc/native_protocol_v1.spec#L656-L658 Description + # of Protocol Error in Apache Cassandra native protocol spec v1 class ProtocolError < ClientError # @private def initialize(message, @@ -519,7 +530,8 @@ def execution_info # Raised when cannot authenticate to Cassandra # - # @see https://github.com/apache/cassandra/blob/cassandra-2.0.16/doc/native_protocol_v1.spec#L659-L660 Description of Bad Credentials Error in Apache Cassandra native protocol spec v1 + # @see https://github.com/apache/cassandra/blob/cassandra-2.0.16/doc/native_protocol_v1.spec#L659-L660 Description + # of Bad Credentials Error in Apache Cassandra native protocol spec v1 class AuthenticationError < ClientError # @private def initialize(message, @@ -603,7 +615,8 @@ def execution_info # @note Seeing this error can be considered a Ruby Driver bug as it should # handle automatic re-preparing internally. # - # @see https://github.com/apache/cassandra/blob/cassandra-2.0.16/doc/native_protocol_v1.spec#L738-L741 Description of Unprepared Error in Apache Cassandra native protocol spec v1 + # @see https://github.com/apache/cassandra/blob/cassandra-2.0.16/doc/native_protocol_v1.spec#L738-L741 Description + # of Unprepared Error in Apache Cassandra native protocol spec v1 class UnpreparedError < ::StandardError include ValidationError # @return [String] prepared statement id that triggered the error @@ -635,14 +648,16 @@ def initialize(message, # Raised when the submitted query has a syntax error. # - # @see https://github.com/apache/cassandra/blob/cassandra-2.0.16/doc/native_protocol_v1.spec#L723 Description of Syntax Error in Apache Cassandra native protocol spec v1 + # @see https://github.com/apache/cassandra/blob/cassandra-2.0.16/doc/native_protocol_v1.spec#L723 Description of + # Syntax Error in Apache Cassandra native protocol spec v1 class SyntaxError < ::StandardError include ValidationError end # Raised when the logged user doesn't have the right to perform the query. # - # @see https://github.com/apache/cassandra/blob/cassandra-2.0.16/doc/native_protocol_v1.spec#L724-L725 Description of Unauthorized Error in Apache Cassandra native protocol spec v1 + # @see https://github.com/apache/cassandra/blob/cassandra-2.0.16/doc/native_protocol_v1.spec#L724-L725 Description + # of Unauthorized Error in Apache Cassandra native protocol spec v1 class UnauthorizedError < ::StandardError include ValidationError end @@ -655,7 +670,8 @@ class UnauthorizedError < ::StandardError # rescue Cassandra::Errors::InvalidError # end # - # @see https://github.com/apache/cassandra/blob/cassandra-2.0.16/doc/native_protocol_v1.spec#L726 Description of Invalid Error in Apache Cassandra native protocol spec v1 + # @see https://github.com/apache/cassandra/blob/cassandra-2.0.16/doc/native_protocol_v1.spec#L726 Description + # of Invalid Error in Apache Cassandra native protocol spec v1 class InvalidError < ::StandardError include ValidationError end @@ -668,7 +684,8 @@ class InvalidError < ::StandardError # rescue Cassandra::Errors::ConfigurationError # end # - # @see https://github.com/apache/cassandra/blob/cassandra-2.0.16/doc/native_protocol_v1.spec#L727 Description of Config Error in Apache Cassandra native protocol spec v1 + # @see https://github.com/apache/cassandra/blob/cassandra-2.0.16/doc/native_protocol_v1.spec#L727 Description of + # Config Error in Apache Cassandra native protocol spec v1 class ConfigurationError < ::StandardError include ValidationError end @@ -685,7 +702,8 @@ class ConfigurationError < ::StandardError # p ['already exists', e.keyspace, e.table] # end # - # @see https://github.com/apache/cassandra/blob/cassandra-2.0.16/doc/native_protocol_v1.spec#L728-L737 Description of Already Exists Error in Apache Cassandra native protocol spec v1 + # @see https://github.com/apache/cassandra/blob/cassandra-2.0.16/doc/native_protocol_v1.spec#L728-L737 Description + # of Already Exists Error in Apache Cassandra native protocol spec v1 class AlreadyExistsError < ConfigurationError # @return [String] keyspace attr_reader :keyspace diff --git a/lib/cassandra/execution/options.rb b/lib/cassandra/execution/options.rb index b9ee0435c..333998787 100644 --- a/lib/cassandra/execution/options.rb +++ b/lib/cassandra/execution/options.rb @@ -52,8 +52,10 @@ class Options # @return [nil, Hash] custom outgoing payload, a map of # string and byte buffers. # - # @see https://github.com/apache/cassandra/blob/33f1edcce97779c971d4f78712a9a8bf014ffbbc/doc/native_protocol_v4.spec#L127-L133 Description of custom payload in Cassandra native protocol v4. - # @see https://datastax.github.io/java-driver/manual/custom_payloads/#enabling-custom-payloads-on-c-nodes Enabling custom payloads on Cassandra nodes. + # @see https://github.com/apache/cassandra/blob/cassandra-3.4/doc/native_protocol_v4.spec#L125-L131 Description + # of custom payload in Cassandra native protocol v4. + # @see https://datastax.github.io/java-driver/manual/custom_payloads/#enabling-custom-payloads-on-c-nodes + # Enabling custom payloads on Cassandra nodes. # # @example Sending a custom payload # result = session.execute(payload: { diff --git a/lib/cassandra/future.rb b/lib/cassandra/future.rb index d52fea57a..ef4febe76 100644 --- a/lib/cassandra/future.rb +++ b/lib/cassandra/future.rb @@ -46,9 +46,7 @@ def failure(error) # @private class Error < Future def initialize(error) - unless error.is_a?(::Exception) - raise ::ArgumentError, "error must be an exception, #{error.inspect} given" - end + raise ::ArgumentError, "error must be an exception, #{error.inspect} given" unless error.is_a?(::Exception) @error = error end @@ -519,9 +517,7 @@ def initialize(executor) end def failure(error) - unless error.is_a?(::Exception) - raise ::ArgumentError, "error must be an exception, #{error.inspect} given" - end + raise ::ArgumentError, "error must be an exception, #{error.inspect} given" unless error.is_a?(::Exception) return unless @state == :pending @@ -600,9 +596,7 @@ def get(timeout = nil) timeout &&= Float(timeout) if timeout - if timeout < 0 - raise ::ArgumentError, "timeout cannot be negative, #{timeout.inspect} given" - end + raise ::ArgumentError, "timeout cannot be negative, #{timeout.inspect} given" if timeout < 0 start = ::Time.now now = start diff --git a/lib/cassandra/host.rb b/lib/cassandra/host.rb index 0dc8a5bfe..e63b72a87 100644 --- a/lib/cassandra/host.rb +++ b/lib/cassandra/host.rb @@ -37,7 +37,9 @@ class Host attr_reader :tokens # @return [Symbol] host status. Must be `:up` or `:down` attr_reader :status - # @note This is the public IP address of the host if the cluster is deployed across multiple Amazon EC2 regions (or equivalently multiple networks). Cassandra nodes in other EC2 regions use this address to connect to this host. + # @note This is the public IP address of the host if the cluster is deployed across multiple Amazon EC2 regions + # (or equivalently multiple networks). Cassandra nodes in other EC2 regions use this address to connect to this + # host. # @return [IPAddr, String] broadcast address, if available. attr_reader :broadcast_address # @note This is the address that other Cassandra nodes use to connect to this host. diff --git a/lib/cassandra/index.rb b/lib/cassandra/index.rb index 856137594..900576afe 100644 --- a/lib/cassandra/index.rb +++ b/lib/cassandra/index.rb @@ -71,10 +71,10 @@ def to_cql def eql?(other) other.is_a?(Index) && @table == other.table && - @name == other.name && - @kind == other.kind && - @target == other.target && - @options == other.options + @name == other.name && + @kind == other.kind && + @target == other.target && + @options == other.options end alias == eql? diff --git a/lib/cassandra/keyspace.rb b/lib/cassandra/keyspace.rb index 730bc49ec..81363fd9b 100644 --- a/lib/cassandra/keyspace.rb +++ b/lib/cassandra/keyspace.rb @@ -411,7 +411,7 @@ def raw_tables def raw_materialized_views @views end - + # @private def raw_types @types diff --git a/lib/cassandra/load_balancing/policies/dc_aware_round_robin.rb b/lib/cassandra/load_balancing/policies/dc_aware_round_robin.rb index f319e65a8..1d15efe73 100644 --- a/lib/cassandra/load_balancing/policies/dc_aware_round_robin.rb +++ b/lib/cassandra/load_balancing/policies/dc_aware_round_robin.rb @@ -20,6 +20,11 @@ module Cassandra module LoadBalancing module Policies class DCAwareRoundRobin < Policy + # @private + LOCAL_CONSISTENCIES = [:local_quorum, :local_one].freeze + # @private + EMPTY_ARRAY = [].freeze + # @private class Plan def initialize(local, remote, index) @@ -62,9 +67,7 @@ def initialize(datacenter = nil, datacenter &&= String(datacenter) max_remote_hosts_to_use &&= Integer(max_remote_hosts_to_use) - unless datacenter.nil? - Util.assert_not_empty(datacenter) { 'datacenter cannot be empty' } - end + Util.assert_not_empty(datacenter) { 'datacenter cannot be empty' } unless datacenter.nil? unless max_remote_hosts_to_use.nil? Util.assert(max_remote_hosts_to_use >= 0) do @@ -145,13 +148,6 @@ def plan(keyspace, statement, options) Plan.new(local, remote, position) end - - private - - # @private - LOCAL_CONSISTENCIES = [:local_quorum, :local_one].freeze - # @private - EMPTY_ARRAY = [].freeze end end end diff --git a/lib/cassandra/materialized_view.rb b/lib/cassandra/materialized_view.rb index be30688e4..e47851f39 100644 --- a/lib/cassandra/materialized_view.rb +++ b/lib/cassandra/materialized_view.rb @@ -21,7 +21,6 @@ module Cassandra # @see Cassandra::Keyspace#each_materialized_view # @see Cassandra::Keyspace#materialized_view class MaterializedView < ColumnContainer - # @return [Table] the table that this materialized view applies to. attr_reader :base_table @@ -46,22 +45,22 @@ def initialize(keyspace, def to_cql keyspace_name = Util.escape_name(@keyspace.name) cql = "CREATE MATERIALIZED VIEW #{keyspace_name}.#{Util.escape_name(@name)} AS\nSELECT " - if @include_all_columns - cql << '*' - else - cql << @columns.map do |column| - Util.escape_name(column.name) - end.join(', ') - end + cql << if @include_all_columns + '*' + else + @columns.map do |column| + Util.escape_name(column.name) + end.join(', ') + end cql << "\nFROM #{keyspace_name}.#{Util.escape_name(@base_table.name)}" cql << "\nWHERE #{@where_clause}" if @where_clause cql << "\nPRIMARY KEY((" cql << @partition_key.map do |column| Util.escape_name(column.name) end.join(', ') - cql << ")" + cql << ')' unless @clustering_columns.empty? - cql << "," + cql << ',' cql << @clustering_columns.map do |column| Util.escape_name(column.name) end.join(', ') @@ -72,10 +71,10 @@ def to_cql # @private def eql?(other) other.is_a?(MaterializedView) && - super.eql?(other) && - @include_all_columns == other.include_all_columns && - @where_clause == other.where_clause && - @base_table == other.base_table + super.eql?(other) && + @include_all_columns == other.include_all_columns && + @where_clause == other.where_clause && + @base_table == other.base_table end alias == eql? diff --git a/lib/cassandra/protocol/coder.rb b/lib/cassandra/protocol/coder.rb index 5ff7fc3ab..4538f50e4 100644 --- a/lib/cassandra/protocol/coder.rb +++ b/lib/cassandra/protocol/coder.rb @@ -26,7 +26,7 @@ module Coder NO_METADATA_FLAG = 0x04 def write_values_v4(buffer, values, types, names = EMPTY_LIST) - if values && values.size > 0 + if values && !values.empty? buffer.append_short(values.size) values.zip(types, names) do |(value, type, name)| buffer.append_string(name) if name @@ -320,7 +320,7 @@ def read_value_v4(buffer, type) end def write_values_v3(buffer, values, types, names = EMPTY_LIST) - if values && values.size > 0 + if values && !values.empty? buffer.append_short(values.size) values.zip(types, names) do |(value, type, name)| buffer.append_string(name) if name @@ -571,7 +571,7 @@ def read_type_v3(buffer) end def write_values_v1(buffer, values, types) - if values && values.size > 0 + if values && !values.empty? buffer.append_short(values.size) values.each_with_index do |value, index| write_value_v1(buffer, value, types[index]) diff --git a/lib/cassandra/protocol/cql_byte_buffer.rb b/lib/cassandra/protocol/cql_byte_buffer.rb index 955714080..b4051e089 100644 --- a/lib/cassandra/protocol/cql_byte_buffer.rb +++ b/lib/cassandra/protocol/cql_byte_buffer.rb @@ -19,6 +19,17 @@ module Cassandra module Protocol class CqlByteBuffer < Ione::ByteBuffer + # @private + MINUS = '-'.freeze + # @private + ZERO = '0'.freeze + # @private + DECIMAL_POINT = '.'.freeze + # @private + FLOAT_STRING_FORMAT = 'F'.freeze + # @private + NO_CHAR = ''.freeze + def inspect "#<#{self.class.name}:0x#{object_id.to_s(16)} #{to_str.inspect}>" end @@ -291,9 +302,7 @@ def append_short_bytes(bytes) def append_consistency(consistency) index = CONSISTENCIES.index(consistency) - if index.nil? || CONSISTENCIES[index].nil? - raise Errors::EncodingError, %(Unknown consistency "#{consistency}") - end + raise Errors::EncodingError, %(Unknown consistency "#{consistency}") if index.nil? || CONSISTENCIES[index].nil? append_short(index) end @@ -360,14 +369,6 @@ def eql?(other) other.eql?(to_str) end alias == eql? - - private - - MINUS = '-'.freeze - ZERO = '0'.freeze - DECIMAL_POINT = '.'.freeze - FLOAT_STRING_FORMAT = 'F'.freeze - NO_CHAR = ''.freeze end end end diff --git a/lib/cassandra/protocol/cql_protocol_handler.rb b/lib/cassandra/protocol/cql_protocol_handler.rb index e359e06b9..d0f8eed1a 100644 --- a/lib/cassandra/protocol/cql_protocol_handler.rb +++ b/lib/cassandra/protocol/cql_protocol_handler.rb @@ -236,9 +236,7 @@ def complete_request(id, response) ensure @lock.unlock end - if response.is_a?(Protocol::SetKeyspaceResultResponse) - @keyspace = response.keyspace - end + @keyspace = response.keyspace if response.is_a?(Protocol::SetKeyspaceResultResponse) if response.is_a?(Protocol::SchemaChangeResultResponse) && response.change == 'DROPPED' && response.keyspace == @keyspace && diff --git a/lib/cassandra/protocol/requests/execute_request.rb b/lib/cassandra/protocol/requests/execute_request.rb index 39dd117f7..39879582c 100644 --- a/lib/cassandra/protocol/requests/execute_request.rb +++ b/lib/cassandra/protocol/requests/execute_request.rb @@ -44,9 +44,7 @@ def initialize(id, unless serial_consistency.nil? || CONSISTENCIES.include?(serial_consistency) raise ArgumentError, %(No such consistency: #{serial_consistency.inspect}) end - if paging_state && !page_size - raise ArgumentError, %(Paging state given but no page size) - end + raise ArgumentError, %(Paging state given but no page size) if paging_state && !page_size super(10, trace) @id = id @metadata = metadata @@ -69,14 +67,14 @@ def write(buffer, protocol_version, encoder) if protocol_version > 1 buffer.append_consistency(@consistency) flags = 0 - flags |= 0x01 if @values.size > 0 + flags |= 0x01 unless @values.empty? flags |= 0x02 unless @request_metadata flags |= 0x04 if @page_size flags |= 0x08 if @paging_state flags |= 0x10 if @serial_consistency flags |= 0x20 if protocol_version > 2 && @timestamp buffer.append(flags.chr) - encoder.write_parameters(buffer, @values, @metadata) if @values.size > 0 + encoder.write_parameters(buffer, @values, @metadata) unless @values.empty? buffer.append_int(@page_size) if @page_size buffer.append_bytes(@paging_state) if @paging_state buffer.append_consistency(@serial_consistency) if @serial_consistency diff --git a/lib/cassandra/protocol/requests/query_request.rb b/lib/cassandra/protocol/requests/query_request.rb index f634aadd7..6ed0d34f1 100644 --- a/lib/cassandra/protocol/requests/query_request.rb +++ b/lib/cassandra/protocol/requests/query_request.rb @@ -60,7 +60,7 @@ def write(buffer, protocol_version, encoder) flags |= 0x08 if @paging_state flags |= 0x10 if @serial_consistency flags |= 0x20 if protocol_version > 2 && @timestamp - if @values && @values.size > 0 + if @values && !@values.empty? flags |= 0x01 flags |= 0x40 if protocol_version > 2 && !@names.empty? buffer.append(flags.chr) diff --git a/lib/cassandra/protocol/requests/startup_request.rb b/lib/cassandra/protocol/requests/startup_request.rb index 48a43fbf4..27d88f899 100644 --- a/lib/cassandra/protocol/requests/startup_request.rb +++ b/lib/cassandra/protocol/requests/startup_request.rb @@ -19,13 +19,16 @@ module Cassandra module Protocol class StartupRequest < Request + # @private + CQL_VERSION = 'CQL_VERSION'.freeze + # @private + COMPRESSION = 'COMPRESSION'.freeze + attr_reader :options def initialize(cql_version, compression = nil) super(1) - unless cql_version - raise ArgumentError, "Invalid CQL version: #{cql_version.inspect}" - end + raise ArgumentError, "Invalid CQL version: #{cql_version.inspect}" unless cql_version @options = {CQL_VERSION => cql_version} @options[COMPRESSION] = compression if compression end @@ -41,11 +44,6 @@ def write(buffer, protocol_version, encoder) def to_s %(STARTUP #{@options}) end - - private - - CQL_VERSION = 'CQL_VERSION'.freeze - COMPRESSION = 'COMPRESSION'.freeze end end end diff --git a/lib/cassandra/protocol/response.rb b/lib/cassandra/protocol/response.rb index b58fd15ee..c83dab0e2 100644 --- a/lib/cassandra/protocol/response.rb +++ b/lib/cassandra/protocol/response.rb @@ -19,8 +19,7 @@ module Cassandra module Protocol class Response - private - + # @private RESPONSE_TYPES = [ # populated by subclasses ] diff --git a/lib/cassandra/protocol/responses/auth_challenge_response.rb b/lib/cassandra/protocol/responses/auth_challenge_response.rb index b69f7f1ad..1529752e1 100644 --- a/lib/cassandra/protocol/responses/auth_challenge_response.rb +++ b/lib/cassandra/protocol/responses/auth_challenge_response.rb @@ -19,6 +19,9 @@ module Cassandra module Protocol class AuthChallengeResponse < Response + # @private + RESPONSE_TYPES[0x0e] = self + attr_reader :token def initialize(token) @@ -28,10 +31,6 @@ def initialize(token) def to_s %(AUTH_CHALLENGE #{@token.bytesize}) end - - private - - RESPONSE_TYPES[0x0e] = self end end end diff --git a/lib/cassandra/protocol/responses/auth_success_response.rb b/lib/cassandra/protocol/responses/auth_success_response.rb index d9d3cbfe4..c42c9726e 100644 --- a/lib/cassandra/protocol/responses/auth_success_response.rb +++ b/lib/cassandra/protocol/responses/auth_success_response.rb @@ -19,6 +19,9 @@ module Cassandra module Protocol class AuthSuccessResponse < Response + # @private + RESPONSE_TYPES[0x10] = self + attr_reader :token def initialize(token) @@ -28,10 +31,6 @@ def initialize(token) def to_s %(AUTH_SUCCESS #{@token && @token.bytesize}) end - - private - - RESPONSE_TYPES[0x10] = self end end end diff --git a/lib/cassandra/protocol/responses/authenticate_response.rb b/lib/cassandra/protocol/responses/authenticate_response.rb index f2bc650bf..38a29a114 100644 --- a/lib/cassandra/protocol/responses/authenticate_response.rb +++ b/lib/cassandra/protocol/responses/authenticate_response.rb @@ -19,6 +19,9 @@ module Cassandra module Protocol class AuthenticateResponse < Response + # @private + RESPONSE_TYPES[0x03] = self + attr_reader :authentication_class def initialize(authentication_class) @@ -28,10 +31,6 @@ def initialize(authentication_class) def to_s %(AUTHENTICATE #{authentication_class}) end - - private - - RESPONSE_TYPES[0x03] = self end end end diff --git a/lib/cassandra/protocol/responses/error_response.rb b/lib/cassandra/protocol/responses/error_response.rb index b0ad9f570..4b784f39a 100644 --- a/lib/cassandra/protocol/responses/error_response.rb +++ b/lib/cassandra/protocol/responses/error_response.rb @@ -19,6 +19,9 @@ module Cassandra module Protocol class ErrorResponse < Response + # @private + RESPONSE_TYPES[0x00] = self + attr_reader :code, :message, :custom_payload, :warnings def initialize(*args) @@ -134,10 +137,6 @@ def to_error(keyspace, statement, options, hosts, consistency, retries) retries) end end - - private - - RESPONSE_TYPES[0x00] = self end end end diff --git a/lib/cassandra/protocol/responses/event_response.rb b/lib/cassandra/protocol/responses/event_response.rb index 95f2fb9e6..520e69986 100644 --- a/lib/cassandra/protocol/responses/event_response.rb +++ b/lib/cassandra/protocol/responses/event_response.rb @@ -19,10 +19,9 @@ module Cassandra module Protocol class EventResponse < ResultResponse - private - + # @private RESPONSE_TYPES[0x0c] = self - + # @private EVENT_TYPES = { # populated by subclasses } diff --git a/lib/cassandra/protocol/responses/prepared_result_response.rb b/lib/cassandra/protocol/responses/prepared_result_response.rb index 83a5856b0..e8d60dcf8 100644 --- a/lib/cassandra/protocol/responses/prepared_result_response.rb +++ b/lib/cassandra/protocol/responses/prepared_result_response.rb @@ -19,6 +19,9 @@ module Cassandra module Protocol class PreparedResultResponse < ResultResponse + # @private + RESULT_TYPES[0x04] = self + attr_reader :id, :metadata, :result_metadata, :pk_idx def initialize(custom_payload, @@ -54,10 +57,6 @@ def to_s hex_id = @id.each_byte.map { |x| x.to_s(16).rjust(2, '0') }.join('') %(RESULT PREPARED #{hex_id} #{@metadata}) end - - private - - RESULT_TYPES[0x04] = self end end end diff --git a/lib/cassandra/protocol/responses/ready_response.rb b/lib/cassandra/protocol/responses/ready_response.rb index a44ad6be5..e91fda382 100644 --- a/lib/cassandra/protocol/responses/ready_response.rb +++ b/lib/cassandra/protocol/responses/ready_response.rb @@ -19,6 +19,9 @@ module Cassandra module Protocol class ReadyResponse < Response + # @private + RESPONSE_TYPES[0x02] = self + def eql?(rs) rs.is_a?(self.class) end @@ -35,10 +38,6 @@ def hash def to_s 'READY' end - - private - - RESPONSE_TYPES[0x02] = self end end end diff --git a/lib/cassandra/protocol/responses/result_response.rb b/lib/cassandra/protocol/responses/result_response.rb index 4db85461e..71f8c0fdd 100644 --- a/lib/cassandra/protocol/responses/result_response.rb +++ b/lib/cassandra/protocol/responses/result_response.rb @@ -19,6 +19,13 @@ module Cassandra module Protocol class ResultResponse < Response + # @private + RESPONSE_TYPES[0x08] = self + # @private + RESULT_TYPES = [ + # populated by subclasses + ] + attr_reader :custom_payload, :warnings, :trace_id def initialize(custom_payload, warnings, trace_id) @@ -30,14 +37,6 @@ def initialize(custom_payload, warnings, trace_id) def void? false end - - private - - RESPONSE_TYPES[0x08] = self - - RESULT_TYPES = [ - # populated by subclasses - ] end end end diff --git a/lib/cassandra/protocol/responses/rows_result_response.rb b/lib/cassandra/protocol/responses/rows_result_response.rb index ba1a90b5f..a0b568dc2 100644 --- a/lib/cassandra/protocol/responses/rows_result_response.rb +++ b/lib/cassandra/protocol/responses/rows_result_response.rb @@ -19,6 +19,9 @@ module Cassandra module Protocol class RowsResultResponse < ResultResponse + # @private + RESULT_TYPES[0x02] = self + attr_reader :rows, :metadata, :paging_state def initialize(custom_payload, warnings, rows, metadata, paging_state, trace_id) @@ -31,10 +34,6 @@ def initialize(custom_payload, warnings, rows, metadata, paging_state, trace_id) def to_s %(RESULT ROWS #{@metadata} #{@rows}) end - - private - - RESULT_TYPES[0x02] = self end end end diff --git a/lib/cassandra/protocol/responses/schema_change_event_response.rb b/lib/cassandra/protocol/responses/schema_change_event_response.rb index faceedfc3..7f6fe69a7 100644 --- a/lib/cassandra/protocol/responses/schema_change_event_response.rb +++ b/lib/cassandra/protocol/responses/schema_change_event_response.rb @@ -21,6 +21,9 @@ module Protocol class SchemaChangeEventResponse < EventResponse TYPE = 'SCHEMA_CHANGE'.freeze + # @private + EVENT_TYPES[TYPE] = self + attr_reader :change, :keyspace, :name, :target, :arguments def initialize(change, keyspace, name, target, arguments) @@ -65,10 +68,6 @@ def to_s %(EVENT SCHEMA_CHANGE #{@change} #{@target} "#{@keyspace}" "#{@name}") end end - - private - - EVENT_TYPES[TYPE] = self end end end diff --git a/lib/cassandra/protocol/responses/schema_change_result_response.rb b/lib/cassandra/protocol/responses/schema_change_result_response.rb index 7463e05c5..43ce93cdb 100644 --- a/lib/cassandra/protocol/responses/schema_change_result_response.rb +++ b/lib/cassandra/protocol/responses/schema_change_result_response.rb @@ -19,6 +19,9 @@ module Cassandra module Protocol class SchemaChangeResultResponse < ResultResponse + # @private + RESULT_TYPES[0x05] = self + attr_reader :arguments, :change, :keyspace, :name, :target, :type def initialize(custom_payload, @@ -62,10 +65,6 @@ def hash def to_s %(RESULT SCHEMA_CHANGE #{@change} #{@target} "#{@keyspace}" "#{@name}") end - - private - - RESULT_TYPES[0x05] = self end end end diff --git a/lib/cassandra/protocol/responses/set_keyspace_result_response.rb b/lib/cassandra/protocol/responses/set_keyspace_result_response.rb index be41a96ca..bc39f33be 100644 --- a/lib/cassandra/protocol/responses/set_keyspace_result_response.rb +++ b/lib/cassandra/protocol/responses/set_keyspace_result_response.rb @@ -19,6 +19,9 @@ module Cassandra module Protocol class SetKeyspaceResultResponse < ResultResponse + # @private + RESULT_TYPES[0x03] = self + attr_reader :keyspace def initialize(custom_payload, warnings, keyspace, trace_id) @@ -29,10 +32,6 @@ def initialize(custom_payload, warnings, keyspace, trace_id) def to_s %(RESULT SET_KEYSPACE "#{@keyspace}") end - - private - - RESULT_TYPES[0x03] = self end end end diff --git a/lib/cassandra/protocol/responses/status_change_event_response.rb b/lib/cassandra/protocol/responses/status_change_event_response.rb index 8d79017ad..7cb4c5112 100644 --- a/lib/cassandra/protocol/responses/status_change_event_response.rb +++ b/lib/cassandra/protocol/responses/status_change_event_response.rb @@ -21,6 +21,9 @@ module Protocol class StatusChangeEventResponse < EventResponse TYPE = 'STATUS_CHANGE'.freeze + # @private + EVENT_TYPES[TYPE] = self + attr_reader :type, :change, :address, :port def initialize(*args) @@ -31,10 +34,6 @@ def initialize(*args) def to_s %(EVENT #{@type} #{@change} #{@address}:#{@port}) end - - private - - EVENT_TYPES[TYPE] = self end end end diff --git a/lib/cassandra/protocol/responses/supported_response.rb b/lib/cassandra/protocol/responses/supported_response.rb index 8827d3960..844dbd6ed 100644 --- a/lib/cassandra/protocol/responses/supported_response.rb +++ b/lib/cassandra/protocol/responses/supported_response.rb @@ -19,6 +19,9 @@ module Cassandra module Protocol class SupportedResponse < Response + # @private + RESPONSE_TYPES[0x06] = self + attr_reader :options def initialize(options) @@ -28,10 +31,6 @@ def initialize(options) def to_s %(SUPPORTED #{options}) end - - private - - RESPONSE_TYPES[0x06] = self end end end diff --git a/lib/cassandra/protocol/responses/topology_change_event_response.rb b/lib/cassandra/protocol/responses/topology_change_event_response.rb index 51b580aec..74a566a90 100644 --- a/lib/cassandra/protocol/responses/topology_change_event_response.rb +++ b/lib/cassandra/protocol/responses/topology_change_event_response.rb @@ -21,14 +21,13 @@ module Protocol class TopologyChangeEventResponse < StatusChangeEventResponse TYPE = 'TOPOLOGY_CHANGE'.freeze + # @private + EVENT_TYPES[TYPE] = self + def initialize(*args) super @type = TYPE end - - private - - EVENT_TYPES[TYPE] = self end end end diff --git a/lib/cassandra/protocol/responses/void_result_response.rb b/lib/cassandra/protocol/responses/void_result_response.rb index 92656f36e..2aaea898a 100644 --- a/lib/cassandra/protocol/responses/void_result_response.rb +++ b/lib/cassandra/protocol/responses/void_result_response.rb @@ -19,6 +19,9 @@ module Cassandra module Protocol class VoidResultResponse < ResultResponse + # @private + RESULT_TYPES[0x01] = self + def to_s %(RESULT VOID) end @@ -26,10 +29,6 @@ def to_s def void? true end - - private - - RESULT_TYPES[0x01] = self end end end diff --git a/lib/cassandra/protocol/v1.rb b/lib/cassandra/protocol/v1.rb index 36a9a24d6..1b79e95d3 100644 --- a/lib/cassandra/protocol/v1.rb +++ b/lib/cassandra/protocol/v1.rb @@ -155,8 +155,6 @@ def actual_decode(buffer, fields, size) end end - private - CODE_ERROR = 0x00 CODE_READY = 0x02 CODE_AUTHENTICATE = 0x03 @@ -258,9 +256,7 @@ def decode_response(opcode, protocol_version, buffer, size, trace_id) id = buffer.read_short_bytes params_metadata = Coder.read_metadata_v1(buffer).first result_metadata = nil - if protocol_version > 1 - result_metadata = Coder.read_metadata_v1(buffer).first - end + result_metadata = Coder.read_metadata_v1(buffer).first if protocol_version > 1 PreparedResultResponse.new(nil, nil, diff --git a/lib/cassandra/protocol/v3.rb b/lib/cassandra/protocol/v3.rb index be5630b8a..ae6e67cf7 100644 --- a/lib/cassandra/protocol/v3.rb +++ b/lib/cassandra/protocol/v3.rb @@ -290,9 +290,7 @@ def decode_response(opcode, protocol_version, buffer, size, trace_id) id = buffer.read_short_bytes params_metadata = Coder.read_metadata_v3(buffer).first result_metadata = nil - if protocol_version > 1 - result_metadata = Coder.read_metadata_v3(buffer).first - end + result_metadata = Coder.read_metadata_v3(buffer).first if protocol_version > 1 PreparedResultResponse.new(nil, nil, diff --git a/lib/cassandra/result.rb b/lib/cassandra/result.rb index 7f7241c38..22e43f557 100644 --- a/lib/cassandra/result.rb +++ b/lib/cassandra/result.rb @@ -96,7 +96,8 @@ def next_page_async(options = nil) # undefined and will likely cause a server process of the coordinator of # such request to abort. # - # @see https://github.com/apache/cassandra/blob/cassandra-2.0.16/doc/native_protocol_v2.spec#L482-L487 Paging State description in Cassandra Native Protocol v2 specification + # @see https://github.com/apache/cassandra/blob/cassandra-2.0.16/doc/native_protocol_v2.spec#L482-L487 Paging State + # description in Cassandra Native Protocol v2 specification def paging_state end end diff --git a/lib/cassandra/retry/policies/downgrading_consistency.rb b/lib/cassandra/retry/policies/downgrading_consistency.rb index 81d81bc72..83ddb9842 100644 --- a/lib/cassandra/retry/policies/downgrading_consistency.rb +++ b/lib/cassandra/retry/policies/downgrading_consistency.rb @@ -24,9 +24,7 @@ class DowngradingConsistency def read_timeout(statement, consistency, required, received, retrieved, retries) return reraise if retries > 0 || SERIAL_CONSISTENCIES.include?(consistency) - if received < required - return max_likely_to_work(consistency, required, received) - end + return max_likely_to_work(consistency, required, received) if received < required retrieved ? reraise : try_again(consistency) end diff --git a/lib/cassandra/statements/prepared.rb b/lib/cassandra/statements/prepared.rb index 2c37d0753..2f98294a3 100644 --- a/lib/cassandra/statements/prepared.rb +++ b/lib/cassandra/statements/prepared.rb @@ -189,9 +189,9 @@ def create_partition_key(values) buffer.discard(4) # discard size else buf = Protocol::CqlByteBuffer.new - partition_key.each do |i| - value = values[i] - metadata = params_metadata[i] + partition_key.each do |ind| + value = values[ind] + metadata = params_metadata[ind] name = metadata[2] type = metadata[3] diff --git a/lib/cassandra/table.rb b/lib/cassandra/table.rb index a58d8a138..79370ece9 100644 --- a/lib/cassandra/table.rb +++ b/lib/cassandra/table.rb @@ -35,7 +35,7 @@ def initialize(keyspace, clustering_order, id) super(keyspace, name, partition_key, clustering_columns, other_columns, options, id) - @clustering_order = clustering_order.freeze + @clustering_order = clustering_order.freeze @indexes = [] @indexes_hash = {} end @@ -72,9 +72,7 @@ def each_index(&block) def to_cql cql = "CREATE TABLE #{Util.escape_name(@keyspace.name)}.#{Util.escape_name(@name)} (\n" primary_key = nil - if @partition_key.one? && @clustering_columns.empty? - primary_key = @partition_key.first.name - end + primary_key = @partition_key.first.name if @partition_key.one? && @clustering_columns.empty? first = true @columns.each do |column| diff --git a/lib/cassandra/time_uuid.rb b/lib/cassandra/time_uuid.rb index 4539ad9e0..577e38047 100644 --- a/lib/cassandra/time_uuid.rb +++ b/lib/cassandra/time_uuid.rb @@ -23,6 +23,11 @@ module Cassandra class TimeUuid < Uuid include Comparable + # @private + LOWER_HALF_MASK = 0xffffffff_ffffffff + # @private + GREGORIAN_OFFSET = 122192928000000000 + # Returns the time component from this UUID as a Time. # # @return [Time] @@ -69,12 +74,5 @@ def time_bits t |= (n & 0xffffffff00000000) >> 32 t end - - private - - # @private - LOWER_HALF_MASK = 0xffffffff_ffffffff - # @private - GREGORIAN_OFFSET = 122192928000000000 end end diff --git a/lib/cassandra/tuple.rb b/lib/cassandra/tuple.rb index 6f74fbf07..984daff1e 100644 --- a/lib/cassandra/tuple.rb +++ b/lib/cassandra/tuple.rb @@ -40,16 +40,12 @@ def [](i) def fetch(i) i = Integer(i) - if i < 0 || i >= @types.size - raise ::IndexError, "index #{i} is outside of tuple, size: #{@types.size}" - end + raise ::IndexError, "index #{i} is outside of tuple, size: #{@types.size}" if i < 0 || i >= @types.size @values[i] end def []=(i, value) - if i < 0 || i >= @types.size - raise ::IndexError, "index #{i} is outside of tuple, size: #{@types.size}" - end + raise ::IndexError, "index #{i} is outside of tuple, size: #{@types.size}" if i < 0 || i >= @types.size Util.assert_type(@types[i], value) @values[i] = value end @@ -90,9 +86,7 @@ def [](i) # @return [Object] value of the tuple at position `i` def fetch(i) i = Integer(i) - if i < 0 || i >= @values.size - raise ::IndexError, "index #{i} is outside of tuple, size: #{@values.size}" - end + raise ::IndexError, "index #{i} is outside of tuple, size: #{@values.size}" if i < 0 || i >= @values.size @values[i] end @@ -103,9 +97,7 @@ def fetch(i) # @return [Object] value of the tuple at position `i` def []=(i, value) i = Integer(i) - if i < 0 || i >= @values.size - raise ::IndexError, "index #{i} is outside of tuple, size: #{@values.size}" - end + raise ::IndexError, "index #{i} is outside of tuple, size: #{@values.size}" if i < 0 || i >= @values.size @values[i] = value end diff --git a/lib/cassandra/udt.rb b/lib/cassandra/udt.rb index c59f11c5e..d2de7b76a 100644 --- a/lib/cassandra/udt.rb +++ b/lib/cassandra/udt.rb @@ -313,9 +313,7 @@ def [](field) def fetch(field) case field when ::Integer - if field >= 0 && field < @values.size - raise ::IndexError, "Field index #{field.inspect} is not present" - end + raise ::IndexError, "Field index #{field.inspect} is not present" if field >= 0 && field < @values.size @values[field][1] when ::String @@ -361,9 +359,7 @@ def has_field?(field) def []=(field, value) case field when ::Integer - if field < 0 || field >= @values.size - raise ::IndexError, "Field index #{field.inspect} is not present" - end + raise ::IndexError, "Field index #{field.inspect} is not present" if field < 0 || field >= @values.size @values[field][1] = value when ::String diff --git a/lib/cassandra/uuid.rb b/lib/cassandra/uuid.rb index e785efa8c..846b879d3 100644 --- a/lib/cassandra/uuid.rb +++ b/lib/cassandra/uuid.rb @@ -25,6 +25,13 @@ module Cassandra # If you want to generate UUIDs see {Cassandra::Uuid::Generator}. # class Uuid + # @private + RAW_FORMAT = '%032x'.force_encoding(Encoding::ASCII).freeze + # @private + HYPHEN = '-'.force_encoding(Encoding::ASCII).freeze + # @private + EMPTY_STRING = ''.freeze + # Creates a new UUID either from a string (expected to be on the standard 8-4-4-4-12 # form, or just 32 characters without hyphens), or from a 128 bit number. # @@ -78,15 +85,6 @@ def eql?(other) end alias == eql? - private - - # @private - RAW_FORMAT = '%032x'.force_encoding(Encoding::ASCII).freeze - # @private - HYPHEN = '-'.force_encoding(Encoding::ASCII).freeze - # @private - EMPTY_STRING = ''.freeze - if RUBY_ENGINE == 'jruby' # @private HEX_RE = /^[A-Fa-f0-9]+$/ @@ -94,21 +92,15 @@ def eql?(other) # @private def from_s(str) str = str.gsub(HYPHEN, EMPTY_STRING) - unless str.length == 32 - raise ::ArgumentError, "Expected 32 hexadecimal digits but got #{str.length}" - end - unless str =~ HEX_RE - raise ::ArgumentError, "invalid value for Integer(): \"#{str}\"" - end + raise ::ArgumentError, "Expected 32 hexadecimal digits but got #{str.length}" unless str.length == 32 + raise ::ArgumentError, "invalid value for Integer(): \"#{str}\"" unless str =~ HEX_RE Integer(str, 16) end else # @private def from_s(str) str = str.gsub(HYPHEN, EMPTY_STRING) - unless str.length == 32 - raise ::ArgumentError, "Expected 32 hexadecimal digits but got #{str.length}" - end + raise ::ArgumentError, "Expected 32 hexadecimal digits but got #{str.length}" unless str.length == 32 Integer(str, 16) end end From 223468940feb352f8830a5ce3073a7c5beaa6a53 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Thu, 24 Mar 2016 16:25:50 -0700 Subject: [PATCH 015/196] * Moved attr_boolean to its own module * Fixed up some more boolean methods to use attr_boolean --- lib/cassandra/attr_boolean.rb | 15 ++++++++------- lib/cassandra/cluster/options.rb | 2 ++ lib/cassandra/column_container.rb | 1 + lib/cassandra/errors.rb | 6 ++---- lib/cassandra/protocol/cql_protocol_handler.rb | 7 +++---- lib/cassandra/protocol/request.rb | 9 ++++----- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/lib/cassandra/attr_boolean.rb b/lib/cassandra/attr_boolean.rb index cefcb6590..3495cd2c1 100644 --- a/lib/cassandra/attr_boolean.rb +++ b/lib/cassandra/attr_boolean.rb @@ -19,13 +19,14 @@ # This file monkey-patches Module to have an attr_boolean method to make it easy # for classes to define boolean instance variables with "foo?" reader methods. # Inspired by http://stackoverflow.com/questions/4013591/attr-reader-with-question-mark-in-a-name - -class Module - def attr_boolean(*names) - names.each do |name| - define_method(:"#{name}?") do - res = instance_variable_get(:"@#{name}") - !res.nil? && res +module Cassandra + module AttrBoolean + def attr_boolean(*names) + names.each do |name| + define_method(:"#{name}?") do + res = instance_variable_get(:"@#{name}") + !res.nil? && res + end end end end diff --git a/lib/cassandra/cluster/options.rb b/lib/cassandra/cluster/options.rb index cf57f9c8f..739fd62d3 100644 --- a/lib/cassandra/cluster/options.rb +++ b/lib/cassandra/cluster/options.rb @@ -20,6 +20,8 @@ module Cassandra class Cluster # @private class Options + extend AttrBoolean + attr_reader :auth_provider, :compressor, :connect_timeout, :credentials, :heartbeat_interval, :idle_timeout, :port, :schema_refresh_delay, :schema_refresh_timeout, :ssl diff --git a/lib/cassandra/column_container.rb b/lib/cassandra/column_container.rb index c02dd9bbb..39c4e07be 100644 --- a/lib/cassandra/column_container.rb +++ b/lib/cassandra/column_container.rb @@ -296,6 +296,7 @@ def each_column(&block) # allowing fetchers to create keyspace, table/view, and hook them together without # worrying about chickens and eggs. # NOTE: Ignore the set request if the @keyspace is already set. + # rubocop:disable Style/AccessorMethodName def set_keyspace(keyspace) @keyspace = keyspace unless @keyspace end diff --git a/lib/cassandra/errors.rb b/lib/cassandra/errors.rb index 30402bac2..99d1c61b2 100644 --- a/lib/cassandra/errors.rb +++ b/lib/cassandra/errors.rb @@ -287,6 +287,8 @@ class ReadTimeoutError < ::StandardError # @return [Boolean] whether actual data (as opposed to data checksum) was # present in the received responses. attr_reader :retrieved + alias retrieved? retrieved + # @return [Symbol] the original consistency level for the request, one of # {Cassandra::CONSISTENCIES} attr_reader :consistency @@ -325,10 +327,6 @@ def initialize(message, @required = required @received = received end - - def retrieved? - @retrieved - end end # Raised when a write request fails. diff --git a/lib/cassandra/protocol/cql_protocol_handler.rb b/lib/cassandra/protocol/cql_protocol_handler.rb index d0f8eed1a..aed900533 100644 --- a/lib/cassandra/protocol/cql_protocol_handler.rb +++ b/lib/cassandra/protocol/cql_protocol_handler.rb @@ -251,7 +251,10 @@ def complete_request(id, response) # @private class RequestPromise < Ione::Promise + extend AttrBoolean + attr_reader :request, :timeout + attr_boolean :timed_out def initialize(request, timeout) @request = request @@ -260,10 +263,6 @@ def initialize(request, timeout) super() end - def timed_out? - @timed_out - end - def time_out! unless future.completed? @timed_out = true diff --git a/lib/cassandra/protocol/request.rb b/lib/cassandra/protocol/request.rb index 503c0efb2..0d7b58f2b 100644 --- a/lib/cassandra/protocol/request.rb +++ b/lib/cassandra/protocol/request.rb @@ -19,17 +19,16 @@ module Cassandra module Protocol class Request - attr_reader :opcode, :trace + extend AttrBoolean + + attr_reader :opcode + attr_boolean :trace def initialize(opcode, trace = false) @opcode = opcode @trace = trace end - def trace? - @trace - end - def compressable? true end From a3587f3089987ac90b09decbbf295c26ecc7195b Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Thu, 24 Mar 2016 18:24:27 -0700 Subject: [PATCH 016/196] RUBY-183 - Run rubocop on fetchers.rb and types.rb * Reformat sample code snippets related to UDT's. * Change attr_boolean to return true if object is non-nil, rather than returning the object. --- .rubocop.yml | 5 - lib/cassandra/attr_boolean.rb | 2 +- lib/cassandra/cluster/schema/fetchers.rb | 405 +++++++++++------------ lib/cassandra/types.rb | 157 +++++---- lib/cassandra/udt.rb | 40 ++- 5 files changed, 323 insertions(+), 286 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 59b887c19..67523226d 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -2,13 +2,8 @@ inherit_from: .rubocop_todo.yml #### The following group of customizations is questionable. #### -# We currently exclude types.rb and fetchers.rb because auto-correct results in an -# infinite loop in RuboCop. AllCops: TargetRubyVersion: 1.9 - Exclude: - - 'lib/cassandra/types.rb' - - 'lib/cassandra/cluster/schema/fetchers.rb' # We shouldn't rescue Exception! Lint/RescueException: diff --git a/lib/cassandra/attr_boolean.rb b/lib/cassandra/attr_boolean.rb index 3495cd2c1..7fbb2668e 100644 --- a/lib/cassandra/attr_boolean.rb +++ b/lib/cassandra/attr_boolean.rb @@ -25,7 +25,7 @@ def attr_boolean(*names) names.each do |name| define_method(:"#{name}?") do res = instance_variable_get(:"@#{name}") - !res.nil? && res + !res.nil? && res != false end end end diff --git a/lib/cassandra/cluster/schema/fetchers.rb b/lib/cassandra/cluster/schema/fetchers.rb index 746c7e55b..993951720 100644 --- a/lib/cassandra/cluster/schema/fetchers.rb +++ b/lib/cassandra/cluster/schema/fetchers.rb @@ -26,6 +26,7 @@ module Fetcher COMPRESSION_PACKAGE_PREFIX = 'org.apache.cassandra.io.compress.'.freeze def fetch(connection) + # rubocop:disable Metrics/LineLength Ione::Future.all(select_keyspaces(connection), select_tables(connection), select_columns(connection), @@ -34,10 +35,7 @@ def fetch(connection) select_aggregates(connection), select_materialized_views(connection), select_indexes(connection)) - .map do |(rows_keyspaces, rows_tables, rows_columns, - rows_types, rows_functions, rows_aggregates, - rows_views, rows_indexes)| - + .map do |rows_keyspaces, rows_tables, rows_columns, rows_types, rows_functions, rows_aggregates, rows_views, rows_indexes| lookup_tables = map_rows_by(rows_tables, 'keyspace_name') lookup_columns = map_rows_by(rows_columns, 'keyspace_name') lookup_types = map_rows_by(rows_types, 'keyspace_name') @@ -70,9 +68,7 @@ def fetch_keyspace(connection, keyspace_name) select_keyspace_aggregates(connection, keyspace_name), select_keyspace_materialized_views(connection, keyspace_name), select_keyspace_indexes(connection, keyspace_name)) - .map do |(rows_keyspaces, rows_tables, rows_columns, - rows_types, rows_functions, rows_aggregates, - rows_views, rows_indexes)| + .map do |rows_keyspaces, rows_tables, rows_columns, rows_types, rows_functions, rows_aggregates, rows_views, rows_indexes| if rows_keyspaces.empty? nil else @@ -106,15 +102,15 @@ def fetch_table(connection, keyspace_name, table_name) def fetch_materialized_view(connection, keyspace_name, view_name) Ione::Future.all(select_materialized_view(connection, keyspace_name, view_name), select_table_columns(connection, keyspace_name, view_name)) - .map do |(rows_views, rows_columns)| + .map do |rows_views, rows_columns| if rows_views.empty? nil else view_row = rows_views.first base_table = @schema.keyspace(keyspace_name).table(view_row['base_table_name']) create_materialized_view(view_row, - rows_columns, - base_table) + rows_columns, + base_table) end end end @@ -130,8 +126,8 @@ def fetch_type(connection, keyspace_name, type_name) end def fetch_function(connection, keyspace_name, function_name, function_args) - select_function(connection, keyspace_name, function_name, function_args). - map do |rows_functions| + select_function(connection, keyspace_name, function_name, function_args) + .map do |rows_functions| if rows_functions.empty? nil else @@ -141,13 +137,13 @@ def fetch_function(connection, keyspace_name, function_name, function_args) end def fetch_aggregate(connection, keyspace_name, aggregate_name, aggregate_args) - select_aggregate(connection, keyspace_name, aggregate_name, aggregate_args). - map do |rows_aggregates| + select_aggregate(connection, keyspace_name, aggregate_name, aggregate_args) + .map do |rows_aggregates| if rows_aggregates.empty? nil else - create_aggregate(rows_aggregates.first, @schema.keyspace(keyspace_name). - send(:raw_functions)) + create_aggregate(rows_aggregates.first, @schema.keyspace(keyspace_name) + .send(:raw_functions)) end end end @@ -249,7 +245,7 @@ def select_aggregate(connection, keyspace_name, aggregate_name, aggregate_args) def send_select_request(connection, cql, params = EMPTY_LIST, types = EMPTY_LIST) backtrace = caller connection.send_request( - Protocol::QueryRequest.new(cql, params, types, :one)).map do |r| + Protocol::QueryRequest.new(cql, params, types, :one)).map do |r| case r when Protocol::RowsResultResponse r.rows @@ -266,41 +262,42 @@ def send_select_request(connection, cql, params = EMPTY_LIST, types = EMPTY_LIST def map_rows_by(rows, key_name, &block) rows.each_with_object(::Hash.new { EMPTY_LIST }) do |row, map| key = row[key_name] - map[key] = [] unless map.has_key?(key) + map[key] = [] unless map.key?(key) - if block - map[key] << yield(row) - else - map[key] << row - end + map[key] << if block + yield(row) + else + row + end end end end # @private module Fetchers + # rubocop:disable Style/ClassAndModuleCamelCase class V1_2_x SELECT_KEYSPACES = 'SELECT * FROM system.schema_keyspaces'.freeze SELECT_TABLES = 'SELECT * FROM system.schema_columnfamilies'.freeze SELECT_COLUMNS = 'SELECT * FROM system.schema_columns'.freeze SELECT_KEYSPACE = - 'SELECT * ' \ - 'FROM system.schema_keyspaces ' \ - 'WHERE keyspace_name = \'%s\''.freeze + 'SELECT * ' \ + 'FROM system.schema_keyspaces ' \ + 'WHERE keyspace_name = \'%s\''.freeze SELECT_KEYSPACE_TABLES = - 'SELECT * ' \ - 'FROM system.schema_columnfamilies ' \ - 'WHERE keyspace_name = \'%s\''.freeze + 'SELECT * ' \ + 'FROM system.schema_columnfamilies ' \ + 'WHERE keyspace_name = \'%s\''.freeze SELECT_KEYSPACE_COLUMNS = - 'SELECT * FROM system.schema_columns WHERE keyspace_name = \'%s\''.freeze + 'SELECT * FROM system.schema_columns WHERE keyspace_name = \'%s\''.freeze SELECT_TABLE = - 'SELECT * ' \ - 'FROM system.schema_columnfamilies ' \ - 'WHERE keyspace_name = \'%s\' AND columnfamily_name = \'%s\''.freeze + 'SELECT * ' \ + 'FROM system.schema_columnfamilies ' \ + 'WHERE keyspace_name = \'%s\' AND columnfamily_name = \'%s\''.freeze SELECT_TABLE_COLUMNS = - 'SELECT * ' \ - 'FROM system.schema_columns ' \ - 'WHERE keyspace_name = \'%s\' AND columnfamily_name = \'%s\''.freeze + 'SELECT * ' \ + 'FROM system.schema_columns ' \ + 'WHERE keyspace_name = \'%s\' AND columnfamily_name = \'%s\''.freeze include Cassandra::Cluster::Schema::Fetcher @@ -328,20 +325,20 @@ def select_keyspace(connection, keyspace_name) end def select_keyspace_tables(connection, keyspace_name) - send_select_request(connection, SELECT_KEYSPACE_TABLES % keyspace_name) + send_select_request(connection, format(SELECT_KEYSPACE_TABLES, keyspace_name)) end def select_keyspace_columns(connection, keyspace_name) - send_select_request(connection, SELECT_KEYSPACE_COLUMNS % keyspace_name) + send_select_request(connection, format(SELECT_KEYSPACE_COLUMNS, keyspace_name)) end def select_table(connection, keyspace_name, table_name) - send_select_request(connection, SELECT_TABLE % [keyspace_name, table_name]) + send_select_request(connection, format(SELECT_TABLE, keyspace_name, table_name)) end def select_table_columns(connection, keyspace_name, table_name) send_select_request(connection, - SELECT_TABLE_COLUMNS % [keyspace_name, table_name]) + format(SELECT_TABLE_COLUMNS, keyspace_name, table_name)) end def create_replication(keyspace_data) @@ -356,9 +353,9 @@ def create_keyspace(keyspace_data, rows_tables, rows_columns, rows_views, rows_indexes) keyspace_name = keyspace_data['keyspace_name'] replication = create_replication(keyspace_data) - types = rows_types.each_with_object({}) do |row, types| - types[row['type_name']] = create_type(row) - end + types = rows_types.each_with_object({}) do |row, h| + h[row['type_name']] = create_type(row) + end # Create a FunctionCollection for the functions and aggregates. functions = Cassandra::FunctionCollection.new @@ -372,10 +369,10 @@ def create_keyspace(keyspace_data, rows_tables, rows_columns, end lookup_columns = map_rows_by(rows_columns, 'columnfamily_name') - tables = rows_tables.each_with_object({}) do |row, tables| + tables = rows_tables.each_with_object({}) do |row, h| table_name = row['columnfamily_name'] # rows_indexes is nil for C* < 3.0. - tables[table_name] = create_table(row, lookup_columns[table_name], nil) + h[table_name] = create_table(row, lookup_columns[table_name], nil) end Keyspace.new(keyspace_name, @@ -411,7 +408,7 @@ def create_table(table_data, rows_columns, rows_indexes) has_value = false clustering_size = size - 2 elsif column_aliases.size == size - 1 && - comparator.results.last.first == Cassandra::Types.varchar + comparator.results.last.first == Cassandra::Types.varchar is_compact = false has_value = false clustering_size = size - 1 @@ -430,12 +427,12 @@ def create_table(table_data, rows_columns, rows_indexes) compaction_strategy = create_compaction_strategy(table_data) table_options = - create_table_options(table_data, compaction_strategy, is_compact) + create_table_options(table_data, compaction_strategy, is_compact) key_aliases = ::JSON.load(table_data['key_aliases']) key_validator.results.each_with_index do |(type, order, is_frozen), i| - key_alias = key_aliases.fetch(i) { i.zero? ? "key" : "key#{i + 1}" } + key_alias = key_aliases.fetch(i) { i.zero? ? 'key' : "key#{i + 1}" } partition_key[i] = Column.new(key_alias, type, order, false, is_frozen) end @@ -445,7 +442,7 @@ def create_table(table_data, rows_columns, rows_indexes) type, order, is_frozen = comparator.results.fetch(i) clustering_columns[i] = - Column.new(column_alias, type, order, false, is_frozen) + Column.new(column_alias, type, order, false, is_frozen) clustering_order[i] = order end @@ -455,9 +452,9 @@ def create_table(table_data, rows_columns, rows_indexes) unless value_alias.empty? type, order, is_frozen = - @type_parser.parse(table_data['default_validator']).results.first + @type_parser.parse(table_data['default_validator']).results.first other_columns << - Column.new(value_alias, type, order, false, is_frozen) + Column.new(value_alias, type, order, false, is_frozen) end end @@ -471,13 +468,13 @@ def create_table(table_data, rows_columns, rows_indexes) end table = Cassandra::Table.new(@schema.keyspace(keyspace_name), - table_name, - partition_key, - clustering_columns, - other_columns, - table_options, - clustering_order, - table_data['id']) + table_name, + partition_key, + clustering_columns, + other_columns, + table_options, + clustering_order, + table_data['id']) # Create Index objects and add them to the table. index_rows.each do |column, row| @@ -494,9 +491,9 @@ def create_index(table, column, row_column) options = ::JSON.load(row_column['index_options']) end column_name = Util.escape_name(column.name) - target = if options.has_key?('index_keys') + target = if options.key?('index_keys') "keys(#{column_name})" - elsif options.has_key?('index_keys_and_values') + elsif options.key?('index_keys_and_values') "entries(#{column_name})" elsif column.frozen? && (column.type == Cassandra::Types::Set || column.type == Cassandra::Types::List || @@ -507,17 +504,17 @@ def create_index(table, column, row_column) end table.add_index(Cassandra::Index.new(table, - row_column['index_name'], - row_column['index_type'].downcase.to_sym, - target, - options)) + row_column['index_name'], + row_column['index_type'].downcase.to_sym, + target, + options)) end def create_column(column_data) name = column_data['column_name'] is_static = (column_data['type'] == 'STATIC') type, order, is_frozen = - @type_parser.parse(column_data['validator']).results.first + @type_parser.parse(column_data['validator']).results.first Column.new(name, type, order, is_static, is_frozen) end @@ -531,8 +528,8 @@ def create_compaction_strategy(table_data) def create_table_options(table_data, compaction_strategy, is_compact) compression_parameters = ::JSON.load(table_data['compression_parameters']) if compression_parameters['sstable_compression'] - compression_parameters['sstable_compression']. - slice!(COMPRESSION_PACKAGE_PREFIX) + compression_parameters['sstable_compression'] + .slice!(COMPRESSION_PACKAGE_PREFIX) end Cassandra::ColumnContainer::Options.new( table_data['comment'], @@ -560,19 +557,19 @@ def create_table_options(table_data, compaction_strategy, is_compact) class V2_0_x < V1_2_x SELECT_KEYSPACE = - 'SELECT * FROM system.schema_keyspaces WHERE keyspace_name = ?'.freeze + 'SELECT * FROM system.schema_keyspaces WHERE keyspace_name = ?'.freeze SELECT_KEYSPACE_TABLES = - 'SELECT * FROM system.schema_columnfamilies WHERE keyspace_name = ?'.freeze + 'SELECT * FROM system.schema_columnfamilies WHERE keyspace_name = ?'.freeze SELECT_KEYSPACE_COLUMNS = - 'SELECT * FROM system.schema_columns WHERE keyspace_name = ?'.freeze + 'SELECT * FROM system.schema_columns WHERE keyspace_name = ?'.freeze SELECT_TABLE = - 'SELECT * ' \ - 'FROM system.schema_columnfamilies ' \ - 'WHERE keyspace_name = ? AND columnfamily_name = ?'.freeze + 'SELECT * ' \ + 'FROM system.schema_columnfamilies ' \ + 'WHERE keyspace_name = ? AND columnfamily_name = ?'.freeze SELECT_TABLE_COLUMNS = - 'SELECT * ' \ - 'FROM system.schema_columns ' \ - 'WHERE keyspace_name = ? AND columnfamily_name = ?'.freeze + 'SELECT * ' \ + 'FROM system.schema_columns ' \ + 'WHERE keyspace_name = ? AND columnfamily_name = ?'.freeze private @@ -586,7 +583,7 @@ def create_table(table_data, rows_columns, rows_indexes) partition_key = [] clustering_columns = [] clustering_order = [] - other_columns = [] + other_columns = [] index_rows = [] rows_columns.each do |row| @@ -603,9 +600,7 @@ def create_table(table_data, rows_columns, rows_indexes) clustering_columns[ind] = column clustering_order[ind] = column.order - if clustering_size.zero? || ind == clustering_size - clustering_size = ind + 1 - end + clustering_size = ind + 1 if clustering_size.zero? || ind == clustering_size else other_columns << column end @@ -616,18 +611,18 @@ def create_table(table_data, rows_columns, rows_indexes) compaction_strategy = create_compaction_strategy(table_data) is_compact = (clustering_size != comparator.results.size - 1) || - !comparator.collections + !comparator.collections table_options = - create_table_options(table_data, compaction_strategy, is_compact) + create_table_options(table_data, compaction_strategy, is_compact) table = Cassandra::Table.new(@schema.keyspace(keyspace_name), - table_name, - partition_key, - clustering_columns, - other_columns, - table_options, - clustering_order, - table_data['id']) + table_name, + partition_key, + clustering_columns, + other_columns, + table_options, + clustering_order, + table_data['id']) # Create Index objects and add them to the table. index_rows.each do |column, row| @@ -669,8 +664,8 @@ def select_table_columns(connection, keyspace_name, table_name) def create_table_options(table_data, compaction_strategy, is_compact) compression_parameters = ::JSON.load(table_data['compression_parameters']) if compression_parameters['sstable_compression'] - compression_parameters['sstable_compression']. - slice!(COMPRESSION_PACKAGE_PREFIX) + compression_parameters['sstable_compression'] + .slice!(COMPRESSION_PACKAGE_PREFIX) end Cassandra::ColumnContainer::Options.new( table_data['comment'], @@ -699,11 +694,11 @@ def create_table_options(table_data, compaction_strategy, is_compact) class V2_1_x < V2_0_x SELECT_TYPES = 'SELECT * FROM system.schema_usertypes'.freeze SELECT_KEYSPACE_TYPES = - 'SELECT * FROM system.schema_usertypes WHERE keyspace_name = ?'.freeze + 'SELECT * FROM system.schema_usertypes WHERE keyspace_name = ?'.freeze SELECT_TYPE = - 'SELECT * ' \ - 'FROM system.schema_usertypes ' \ - 'WHERE keyspace_name = ? AND type_name = ?'.freeze + 'SELECT * ' \ + 'FROM system.schema_usertypes ' \ + 'WHERE keyspace_name = ? AND type_name = ?'.freeze private @@ -743,8 +738,8 @@ def select_type(connection, keyspace_name, type_name) def create_table_options(table_data, compaction_strategy, is_compact) compression_parameters = ::JSON.load(table_data['compression_parameters']) if compression_parameters['sstable_compression'] - compression_parameters['sstable_compression']. - slice!(COMPRESSION_PACKAGE_PREFIX) + compression_parameters['sstable_compression'] + .slice!(COMPRESSION_PACKAGE_PREFIX) end Cassandra::ColumnContainer::Options.new( table_data['comment'], @@ -774,19 +769,19 @@ class V2_2_x < V2_1_x SELECT_FUNCTIONS = 'SELECT * FROM system.schema_functions'.freeze SELECT_AGGREGATES = 'SELECT * FROM system.schema_aggregates'.freeze SELECT_KEYSPACE_FUNCTIONS = - 'SELECT * FROM system.schema_functions WHERE keyspace_name = ?'.freeze + 'SELECT * FROM system.schema_functions WHERE keyspace_name = ?'.freeze SELECT_KEYSPACE_AGGREGATES = - 'SELECT * FROM system.schema_aggregates WHERE keyspace_name = ?'.freeze + 'SELECT * FROM system.schema_aggregates WHERE keyspace_name = ?'.freeze SELECT_FUNCTION = - 'SELECT * ' \ - 'FROM system.schema_functions ' \ - 'WHERE keyspace_name = ? AND function_name = ? ' \ - 'AND argument_types = ?'.freeze + 'SELECT * ' \ + 'FROM system.schema_functions ' \ + 'WHERE keyspace_name = ? AND function_name = ? ' \ + 'AND argument_types = ?'.freeze SELECT_AGGREGATE = - 'SELECT * ' \ - 'FROM system.schema_aggregates ' \ - 'WHERE keyspace_name = ? AND aggregate_name = ? ' \ - 'AND argument_types = ?'.freeze + 'SELECT * ' \ + 'FROM system.schema_aggregates ' \ + 'WHERE keyspace_name = ? AND aggregate_name = ? ' \ + 'AND argument_types = ?'.freeze # parse an array of string argument types and return an array of # [Cassandra::Type]s. @@ -807,41 +802,41 @@ def create_function(function_data) function_name = function_data['function_name'] function_lang = function_data['language'] function_type = - @type_parser.parse(function_data['return_type']).results.first.first + @type_parser.parse(function_data['return_type']).results.first.first function_body = function_data['body'] called_on_null = function_data['called_on_null_input'] arguments = [] - Array(function_data['argument_names']). - zip(Array(function_data['argument_types'])) do |argument_name, fqcn| + Array(function_data['argument_names']) + .zip(Array(function_data['argument_types'])) do |argument_name, fqcn| argument_type = @type_parser.parse(fqcn).results.first.first arguments << Argument.new(argument_name, argument_type) end Cassandra::Function.new(keyspace_name, - function_name, - function_lang, - function_type, - arguments, - function_body, - called_on_null) + function_name, + function_lang, + function_type, + arguments, + function_body, + called_on_null) end def create_aggregate(aggregate_data, functions) keyspace_name = aggregate_data['keyspace_name'] aggregate_name = aggregate_data['aggregate_name'] aggregate_type = - @type_parser.parse(aggregate_data['return_type']).results.first.first + @type_parser.parse(aggregate_data['return_type']).results.first.first argument_types = aggregate_data['argument_types'].map do |fqcn| @type_parser.parse(fqcn).results.first.first end.freeze state_type = - @type_parser.parse(aggregate_data['state_type']).results.first.first + @type_parser.parse(aggregate_data['state_type']).results.first.first initial_state = Util.encode_object( - Protocol::Coder.read_value_v4( - Protocol::CqlByteBuffer.new.append_bytes(aggregate_data['initcond']), - state_type)) + Protocol::Coder.read_value_v4( + Protocol::CqlByteBuffer.new.append_bytes(aggregate_data['initcond']), + state_type)) # The state-function takes arguments: first the stype, then the args of the aggregate. state_function = functions.get(aggregate_data['state_func'], @@ -895,66 +890,66 @@ def select_aggregate(connection, keyspace_name, aggregate_name, aggregate_args) end class V3_0_x < V2_2_x - SELECT_KEYSPACES = "SELECT * FROM system_schema.keyspaces".freeze - SELECT_TABLES = "SELECT * FROM system_schema.tables".freeze - SELECT_COLUMNS = "SELECT * FROM system_schema.columns".freeze - SELECT_TYPES = "SELECT * FROM system_schema.types".freeze - SELECT_FUNCTIONS = "SELECT * FROM system_schema.functions".freeze - SELECT_AGGREGATES = "SELECT * FROM system_schema.aggregates".freeze - SELECT_INDEXES = "SELECT * FROM system_schema.indexes".freeze - SELECT_VIEWS = "SELECT * FROM system_schema.views".freeze + SELECT_KEYSPACES = 'SELECT * FROM system_schema.keyspaces'.freeze + SELECT_TABLES = 'SELECT * FROM system_schema.tables'.freeze + SELECT_COLUMNS = 'SELECT * FROM system_schema.columns'.freeze + SELECT_TYPES = 'SELECT * FROM system_schema.types'.freeze + SELECT_FUNCTIONS = 'SELECT * FROM system_schema.functions'.freeze + SELECT_AGGREGATES = 'SELECT * FROM system_schema.aggregates'.freeze + SELECT_INDEXES = 'SELECT * FROM system_schema.indexes'.freeze + SELECT_VIEWS = 'SELECT * FROM system_schema.views'.freeze SELECT_KEYSPACE = - 'SELECT * FROM system_schema.keyspaces WHERE keyspace_name = ?'.freeze + 'SELECT * FROM system_schema.keyspaces WHERE keyspace_name = ?'.freeze SELECT_KEYSPACE_TABLES = - 'SELECT * FROM system_schema.tables WHERE keyspace_name = ?'.freeze + 'SELECT * FROM system_schema.tables WHERE keyspace_name = ?'.freeze SELECT_KEYSPACE_INDEXES = - 'SELECT * FROM system_schema.indexes WHERE keyspace_name = ?'.freeze + 'SELECT * FROM system_schema.indexes WHERE keyspace_name = ?'.freeze SELECT_KEYSPACE_COLUMNS = - 'SELECT * FROM system_schema.columns WHERE keyspace_name = ?'.freeze - SELECT_KEYSPACE_VIEWS = - 'SELECT * FROM system_schema.views WHERE keyspace_name = ?'.freeze + 'SELECT * FROM system_schema.columns WHERE keyspace_name = ?'.freeze + SELECT_KEYSPACE_VIEWS = + 'SELECT * FROM system_schema.views WHERE keyspace_name = ?'.freeze SELECT_KEYSPACE_TYPES = - "SELECT * FROM system_schema.types WHERE keyspace_name = ?".freeze + 'SELECT * FROM system_schema.types WHERE keyspace_name = ?'.freeze SELECT_KEYSPACE_FUNCTIONS = - 'SELECT * FROM system_schema.functions WHERE keyspace_name = ?'.freeze + 'SELECT * FROM system_schema.functions WHERE keyspace_name = ?'.freeze SELECT_KEYSPACE_AGGREGATES = - 'SELECT * FROM system_schema.aggregates WHERE keyspace_name = ?'.freeze + 'SELECT * FROM system_schema.aggregates WHERE keyspace_name = ?'.freeze SELECT_TABLE = - 'SELECT * ' \ - 'FROM system_schema.tables ' \ - 'WHERE keyspace_name = ? AND table_name = ?'.freeze + 'SELECT * ' \ + 'FROM system_schema.tables ' \ + 'WHERE keyspace_name = ? AND table_name = ?'.freeze SELECT_TABLE_COLUMNS = - 'SELECT * ' \ - 'FROM system_schema.columns ' \ - 'WHERE keyspace_name = ? AND table_name = ?'.freeze + 'SELECT * ' \ + 'FROM system_schema.columns ' \ + 'WHERE keyspace_name = ? AND table_name = ?'.freeze SELECT_TABLE_INDEXES = - 'SELECT * ' \ - 'FROM system_schema.indexes ' \ - 'WHERE keyspace_name = ? AND table_name = ?'.freeze + 'SELECT * ' \ + 'FROM system_schema.indexes ' \ + 'WHERE keyspace_name = ? AND table_name = ?'.freeze - SELECT_VIEW = - 'SELECT * ' \ - 'FROM system_schema.views ' \ - 'WHERE keyspace_name = ? AND view_name = ?'.freeze + SELECT_VIEW = + 'SELECT * ' \ + 'FROM system_schema.views ' \ + 'WHERE keyspace_name = ? AND view_name = ?'.freeze SELECT_TYPE = - 'SELECT * ' \ - 'FROM system_schema.types ' \ - 'WHERE keyspace_name = ? AND type_name = ?'.freeze + 'SELECT * ' \ + 'FROM system_schema.types ' \ + 'WHERE keyspace_name = ? AND type_name = ?'.freeze SELECT_FUNCTION = - 'SELECT * ' \ - 'FROM system_schema.functions ' \ - 'WHERE keyspace_name = ? AND function_name = ? ' \ - 'AND argument_types = ?'.freeze + 'SELECT * ' \ + 'FROM system_schema.functions ' \ + 'WHERE keyspace_name = ? AND function_name = ? ' \ + 'AND argument_types = ?'.freeze SELECT_AGGREGATE = - 'SELECT * ' \ - 'FROM system_schema.aggregates ' \ - 'WHERE keyspace_name = ? AND aggregate_name = ? ' \ - 'AND argument_types = ?'.freeze + 'SELECT * ' \ + 'FROM system_schema.aggregates ' \ + 'WHERE keyspace_name = ? AND aggregate_name = ? ' \ + 'AND argument_types = ?'.freeze # parse an array of string argument types and return an array of # [Cassandra::Type]s. @@ -1107,19 +1102,19 @@ def create_function(function_data, types = nil) arguments = [] - function_data['argument_names']. - zip(function_data['argument_types']) do |argument_name, argument_type| + function_data['argument_names'] + .zip(function_data['argument_types']) do |argument_name, argument_type| arguments << Argument.new(argument_name, @type_parser.parse(argument_type, types).first) end Cassandra::Function.new(keyspace_name, - function_name, - function_lang, - function_type, - arguments, - function_body, - called_on_null) + function_name, + function_lang, + function_type, + arguments, + function_body, + called_on_null) end def create_aggregate(aggregate_data, functions, types = nil) @@ -1127,7 +1122,7 @@ def create_aggregate(aggregate_data, functions, types = nil) aggregate_name = aggregate_data['aggregate_name'] types ||= @schema.keyspace(keyspace_name).send(:raw_types) aggregate_type = - @type_parser.parse(aggregate_data['return_type'], types).first + @type_parser.parse(aggregate_data['return_type'], types).first argument_types = aggregate_data['argument_types'].map do |argument_type| @type_parser.parse(argument_type, types).first end.freeze @@ -1182,11 +1177,9 @@ def create_types(rows_types, types) break if skipped_rows.empty? - if rows_size == skipped_rows.size - raise "Unable to resolve circular references among UDTs when parsing" - else - rows_types, skipped_rows = skipped_rows, rows_types - end + raise 'Unable to resolve circular references among UDTs when parsing' if rows_size == skipped_rows.size + + rows_types, skipped_rows = skipped_rows, rows_types end end @@ -1215,19 +1208,19 @@ def create_keyspace(keyspace_data, rows_tables, rows_columns, rows_types, lookup_columns = map_rows_by(rows_columns, 'table_name') lookup_indexes = map_rows_by(rows_indexes, 'table_name') - tables = rows_tables.each_with_object({}) do |row, tables| + tables = rows_tables.each_with_object({}) do |row, h| table_name = row['table_name'] - tables[table_name] = create_table(row, lookup_columns[table_name], - lookup_indexes[table_name], types) + h[table_name] = create_table(row, lookup_columns[table_name], + lookup_indexes[table_name], types) end - views = rows_views.each_with_object({}) do |row, views| + views = rows_views.each_with_object({}) do |row, h| view_name = row['view_name'] base_table = tables[row['base_table_name']] - views[view_name] = create_materialized_view(row, - lookup_columns[view_name], - base_table, - types) + h[view_name] = create_materialized_view(row, + lookup_columns[view_name], + base_table, + types) end Keyspace.new(keyspace_name, @@ -1256,9 +1249,7 @@ def create_compaction_strategy(table_data) def create_table_options(table_data, compaction_strategy, is_compact) compression = table_data['compression'] - if compression['class'] - compression['class'].slice!(COMPRESSION_PACKAGE_PREFIX) - end + compression['class'].slice!(COMPRESSION_PACKAGE_PREFIX) if compression['class'] Cassandra::ColumnContainer::Options.new( table_data['comment'], @@ -1285,7 +1276,7 @@ def create_table_options(table_data, compaction_strategy, is_compact) def create_column(column_data, types) name = column_data['column_name'] - is_static = (column_data['kind'].to_s.upcase == 'STATIC') + is_static = column_data['kind'].to_s.casecmp('STATIC').zero? order = column_data['clustering_order'] == 'desc' ? :desc : :asc type, is_frozen = @type_parser.parse(column_data['type'], types) @@ -1306,8 +1297,8 @@ def create_table(table_data, rows_columns, rows_indexes, types = nil) partition_key = [] clustering_columns = [] clustering_order = [] - other_columns = [] - types ||= @schema.keyspace(keyspace_name).send(:raw_types) + other_columns = [] + types ||= @schema.keyspace(keyspace_name).send(:raw_types) rows_columns.each do |row| next if row['column_name'].empty? @@ -1332,16 +1323,16 @@ def create_table(table_data, rows_columns, rows_indexes, types = nil) table_data['crc_check_chance'] ||= 1.0 compaction_strategy = create_compaction_strategy(table_data) table_options = - create_table_options(table_data, compaction_strategy, is_compact) + create_table_options(table_data, compaction_strategy, is_compact) table = Cassandra::Table.new(@schema.keyspace(keyspace_name), - table_name, - partition_key, - clustering_columns, - other_columns, - table_options, - clustering_order, - table_data['id']) + table_name, + partition_key, + clustering_columns, + other_columns, + table_options, + clustering_order, + table_data['id']) rows_indexes.each do |row| create_index(table, row) end @@ -1351,21 +1342,21 @@ def create_table(table_data, rows_columns, rows_indexes, types = nil) def create_index(table, row_index) options = row_index['options'] table.add_index(Cassandra::Index.new(table, row_index['index_name'], - row_index['kind'].downcase.to_sym, - options['target'], options)) + row_index['kind'].downcase.to_sym, + options['target'], options)) end def create_materialized_view(view_data, rows_columns, base_table, types = nil) keyspace_name = view_data['keyspace_name'] view_name = view_data['view_name'] include_all_columns = view_data['include_all_columns'] - where_clause = view_data['where_clause'] + where_clause = view_data['where_clause'] # Separate out partition key, clustering columns, other columns partition_key = [] clustering_columns = [] - other_columns = [] - types ||= @schema.keyspace(keyspace_name).send(:raw_types) + other_columns = [] + types ||= @schema.keyspace(keyspace_name).send(:raw_types) rows_columns.each do |row| next if row['column_name'].empty? @@ -1450,7 +1441,7 @@ def fetch_table(connection, keyspace_name, table_name) def fetch_materialized_view(connection, keyspace_name, view_name) find_fetcher(connection) - .fetch_materialized_view(connection, keyspace_name, view_name) + .fetch_materialized_view(connection, keyspace_name, view_name) rescue => e return Ione::Future.failed(e) end @@ -1496,21 +1487,21 @@ def find_fetcher(connection) unless host ips = @registry.hosts.map(&:ip) raise Errors::ClientError, - "unable to find release version for current host, " \ + 'unable to find release version for current host, ' \ "connected to #{connection.host}, but cluster contains " \ "#{ips}." end version = host.release_version unless version - raise Errors::ClientError, "unable to determine release " \ + raise Errors::ClientError, 'unable to determine release ' \ "version for host: #{host.inspect}" end @fetchers[version] ||= begin current = @versions.find {|v| v.matches?(version)} unless current - raise Errors::ClientError, "unsupported release version " \ + raise Errors::ClientError, 'unsupported release version ' \ "#{version.inspect}." end current.fetcher diff --git a/lib/cassandra/types.rb b/lib/cassandra/types.rb index afb73f90c..a6a368694 100644 --- a/lib/cassandra/types.rb +++ b/lib/cassandra/types.rb @@ -48,7 +48,13 @@ def to_s end end - module Types; extend self + module Types + # If we use module_function, the yard docs end up showing duplicates of all + # methods: one for self, the other as instance methods. + # + # rubocop:disable Style/ModuleFunction + extend self + # @private class Simple < Type def new(value) @@ -71,12 +77,13 @@ def hash def eql?(other) other.is_a?(Simple) && @kind == other.kind end - alias :== :eql? + + alias == eql? private def new_varchar(value) - String(value) + String(value) end def assert_varchar(value, message, &block) @@ -140,7 +147,7 @@ def assert_varint(value, message, &block) end def new_boolean(value) - !!value + !value.nil? && value != false end def assert_boolean(value, message, &block) @@ -652,7 +659,7 @@ def assert(value, message = nil, &block) # @return [String] `"list"` # @see Cassandra::Type#to_s def to_s - "list<#{@value_type.to_s}>" + "list<#{@value_type}>" end def hash @@ -667,7 +674,8 @@ def hash def eql?(other) other.is_a?(List) && @value_type == other.value_type end - alias :== :eql? + + alias == eql? end class Map < Type @@ -677,7 +685,7 @@ class Map < Type # @private def initialize(key_type, value_type) super(:map) - @key_type = key_type + @key_type = key_type @value_type = value_type end @@ -704,7 +712,7 @@ def new(*value) end result else - raise ::ArgumentError, "cannot convert #{value.inspect} to #{to_s}" + raise ::ArgumentError, "cannot convert #{value.inspect} to #{self}" end end @@ -727,7 +735,7 @@ def assert(value, message = nil, &block) # @return [String] `"map"` # @see Cassandra::Type#to_s def to_s - "map<#{@key_type.to_s}, #{@value_type.to_s}>" + "map<#{@key_type}, #{@value_type}>" end def hash @@ -745,7 +753,8 @@ def eql?(other) @key_type == other.key_type && @value_type == other.value_type end - alias :== :eql? + + alias == eql? end class Set < Type @@ -816,7 +825,7 @@ def assert(value, message = nil, &block) # @return [String] `"set"` # @see Cassandra::Type#to_s def to_s - "set<#{@value_type.to_s}>" + "set<#{@value_type}>" end def hash @@ -831,7 +840,8 @@ def hash def eql?(other) other.is_a?(Set) && @value_type == other.value_type end - alias :== :eql? + + alias == eql? end # @!parse @@ -1088,7 +1098,8 @@ def hash def eql?(other) other.is_a?(Tuple) && @members == other.members end - alias :== :eql? + + alias == eql? end # @!parse @@ -1188,7 +1199,8 @@ def eql?(other) @name == other.name && @type == other.type end - alias :== :eql? + + alias == eql? end # @return [String] keyspace where this type is defined @@ -1203,15 +1215,15 @@ def eql?(other) # @private def initialize(keyspace, name, fields) super(:udt) - @keyspace = keyspace - @name = name - @fields = fields + @keyspace = keyspace + @name = name + @fields = fields end # @param name [String] field name # @return [Boolean] whether this type has a given field def has_field?(name) - @fields.any? {|f| f.name == name} + @fields.any? { |f| f.name == name } end # Yield or enumerate each field defined in this type @@ -1228,13 +1240,14 @@ def each_field(&block) @fields.dup end end - alias :fields :each_field + + alias fields each_field # @param name [String] field name # @return [Cassandra::UserDefined::Field, nil] a field with this name or # nil def field(name) - @fields.find {|f| f.name == name} + @fields.find { |f| f.name == name } end # Coerces the value to Cassandra::UDT @@ -1290,7 +1303,7 @@ def assert(value, message = nil, &block) # @see Cassandra::Type#to_s def to_s "#{Util.escape_name(@keyspace)}.#{Util.escape_name(@name)} " \ - "{#{@fields.join(', ')}}" + "{#{@fields.join(', ')}}" end def hash @@ -1310,12 +1323,13 @@ def eql?(other) @name == other.name && @fields == other.fields end - alias :== :eql? + + alias == eql? # Output this type in CQL def to_cql - cql = "CREATE TYPE #{Util.escape_name(@keyspace)}.#{Util.escape_name(@name)} " \ - "(\n" + cql = "CREATE TYPE #{Util.escape_name(@keyspace)}.#{Util.escape_name(@name)} " \ + "(\n" first = true @fields.each do |field| @@ -1346,7 +1360,7 @@ def type_to_cql(type) "frozen <#{Util.escape_name(type.keyspace)}.#{Util.escape_name(type.name)}>" end else - "#{type}" + type.to_s end end end @@ -1396,7 +1410,8 @@ def hash def eql?(other) other.is_a?(Custom) && @name == other.name end - alias :== :eql? + + alias == eql? end # @return [Cassandra::Types::Text] text type since varchar is an alias for text @@ -1503,8 +1518,8 @@ def tinyint # @return [Cassandra::Types::List] list type def list(value_type) Util.assert_instance_of(Cassandra::Type, value_type, - "list type must be a Cassandra::Type, #{value_type.inspect} given" - ) + "list type must be a Cassandra::Type, #{value_type.inspect} given" + ) List.new(value_type) end @@ -1514,11 +1529,11 @@ def list(value_type) # @return [Cassandra::Types::Map] map type def map(key_type, value_type) Util.assert_instance_of(Cassandra::Type, key_type, - "map key type must be a Cassandra::Type, #{key_type.inspect} given" - ) + "map key type must be a Cassandra::Type, #{key_type.inspect} given" + ) Util.assert_instance_of(Cassandra::Type, value_type, - "map value type must be a Cassandra::Type, #{value_type.inspect} given" - ) + "map value type must be a Cassandra::Type, #{value_type.inspect} given" + ) Map.new(key_type, value_type) end @@ -1527,8 +1542,8 @@ def map(key_type, value_type) # @return [Cassandra::Types::Set] set type def set(value_type) Util.assert_instance_of(Cassandra::Type, value_type, - "set type must be a Cassandra::Type, #{value_type.inspect} given" - ) + "set type must be a Cassandra::Type, #{value_type.inspect} given" + ) Set.new(value_type) end @@ -1536,12 +1551,12 @@ def set(value_type) # @param members [*Cassandra::Type] types of members of this tuple # @return [Cassandra::Types::Tuple] tuple type def tuple(*members) - Util.assert_not_empty(members, "tuple must contain at least one member") + Util.assert_not_empty(members, 'tuple must contain at least one member') members.each do |member| Util.assert_instance_of(Cassandra::Type, member, - "each tuple member must be a Cassandra::Type, " \ - "#{member.inspect} given" - ) + 'each tuple member must be a Cassandra::Type, ' \ + "#{member.inspect} given" + ) end Tuple.new(*members) @@ -1551,13 +1566,25 @@ def tuple(*members) # @example Various ways of defining the same UDT # include Cassandra::Types # - # udt('simplex', 'address', {'street' => varchar, 'city' => varchar, 'state' => varchar, 'zip' => varchar}) #=> simplex.address + # udt('simplex', 'address', {'street' => varchar, + # 'city' => varchar, + # 'state' => varchar, + # 'zip' => varchar}) #=> simplex.address # - # udt('simplex', 'address', [['street', varchar], ['city', varchar], ['state', varchar], ['zip', varchar]]) #=> simplex.address + # udt('simplex', 'address', [['street', varchar], + # ['city', varchar], + # ['state', varchar], + # ['zip', varchar]]) #=> simplex.address # - # udt('simplex', 'address', ['street', varchar], ['city', varchar], ['state', varchar], ['zip', varchar]) #=> simplex.address + # udt('simplex', 'address', ['street', varchar], + # ['city', varchar], + # ['state', varchar], + # ['zip', varchar]) #=> simplex.address # - # udt('simplex', 'address', 'street', varchar, 'city', varchar, 'state', varchar, 'zip', varchar) #=> simplex.address + # udt('simplex', 'address', 'street', varchar, + # 'city', varchar, + # 'state', varchar, + # 'zip', varchar) #=> simplex.address # @param keyspace [String] name of the keyspace that this UDT is defined in # @param name [String] name of this UDT # @param fields [Hash, @@ -1567,44 +1594,44 @@ def tuple(*members) # @return [Cassandra::Types::UserDefined] user defined type def udt(keyspace, name, *fields) keyspace = String(keyspace) - name = String(name) - fields = Array(fields.first) if fields.one? + name = String(name) + fields = Array(fields.first) if fields.one? Util.assert_not_empty(fields, - "user-defined type must contain at least one field" - ) + 'user-defined type must contain at least one field' + ) if fields.first.is_a?(::Array) fields = fields.map do |pair| Util.assert(pair.size == 2, - "fields of a user-defined type must be an Array of name and " \ - "value pairs, #{pair.inspect} given" - ) + 'fields of a user-defined type must be an Array of name and ' \ + "value pairs, #{pair.inspect} given" + ) Util.assert_instance_of(::String, pair[0], - "each field name for a user-defined type must be a String, " \ - "#{pair[0].inspect} given" - ) + 'each field name for a user-defined type must be a String, ' \ + "#{pair[0].inspect} given" + ) Util.assert_instance_of(Cassandra::Type, pair[1], - "each field type for a user-defined type must be a " \ - "Cassandra::Type, #{pair[1].inspect} given" - ) + 'each field type for a user-defined type must be a ' \ + "Cassandra::Type, #{pair[1].inspect} given" + ) UserDefined::Field.new(*pair) end else - Util.assert((fields.size % 2) == 0, - "fields of a user-defined type must be an Array of alternating " \ - "names and values pairs, #{fields.inspect} given" - ) + Util.assert(fields.size.even?, + 'fields of a user-defined type must be an Array of alternating ' \ + "names and values pairs, #{fields.inspect} given" + ) fields = fields.each_slice(2).map do |field_name, field_type| Util.assert_instance_of(::String, field_name, - "each field name for a user-defined type must be a String, " \ - "#{field_name.inspect} given" - ) + 'each field name for a user-defined type must be a String, ' \ + "#{field_name.inspect} given" + ) Util.assert_instance_of(Cassandra::Type, field_type, - "each field type for a user-defined type must be a " \ - "Cassandra::Type, #{field_type.inspect} given" - ) + 'each field type for a user-defined type must be a ' \ + "Cassandra::Type, #{field_type.inspect} given" + ) UserDefined::Field.new(field_name, field_type) end diff --git a/lib/cassandra/udt.rb b/lib/cassandra/udt.rb index d2de7b76a..0d9b777d5 100644 --- a/lib/cassandra/udt.rb +++ b/lib/cassandra/udt.rb @@ -181,21 +181,45 @@ def eql?(other) # @param values [Hash, Array>, # *Object, *Array] - UDT field values # @example Various ways of creating the same UDT instance - # Cassandra::UDT.new({'street' => '123 Main St.', 'city' => 'Whatever', 'state' => 'XZ', 'zip' => '10020'}) + # Cassandra::UDT.new({'street' => '123 Main St.', + # 'city' => 'Whatever', + # 'state' => 'XZ', + # 'zip' => '10020'}) # - # Cassandra::UDT.new(street: '123 Main St.', city: 'Whatever', state: 'XZ', zip: '10020') + # Cassandra::UDT.new(street: '123 Main St.', + # city: 'Whatever', + # state: 'XZ', + # zip: '10020') # - # Cassandra::UDT.new('street', '123 Main St.', 'city', 'Whatever', 'state', 'XZ', 'zip', '10020') + # Cassandra::UDT.new('street', '123 Main St.', + # 'city', 'Whatever', + # 'state', 'XZ', + # 'zip', '10020') # - # Cassandra::UDT.new(:street, '123 Main St.', :city, 'Whatever', :state, 'XZ', :zip, '10020') + # Cassandra::UDT.new(:street, '123 Main St.', + # :city, 'Whatever', + # :state, 'XZ', + # :zip, '10020') # - # Cassandra::UDT.new(['street', '123 Main St.'], ['city', 'Whatever'], ['state', 'XZ'], ['zip', '10020']) + # Cassandra::UDT.new(['street', '123 Main St.'], + # ['city', 'Whatever'], + # ['state', 'XZ'], + # ['zip', '10020']) # - # Cassandra::UDT.new([:street, '123 Main St.'], [:city, 'Whatever'], [:state, 'XZ'], [:zip, '10020']) + # Cassandra::UDT.new([:street, '123 Main St.'], + # [:city, 'Whatever'], + # [:state, 'XZ'], + # [:zip, '10020']) # - # Cassandra::UDT.new([['street', '123 Main St.'], ['city', 'Whatever'], ['state', 'XZ'], ['zip', '10020']]) + # Cassandra::UDT.new([['street', '123 Main St.'], + # ['city', 'Whatever'], + # ['state', 'XZ'], + # ['zip', '10020']]) # - # Cassandra::UDT.new([[:street, '123 Main St.'], [:city, 'Whatever'], [:state, 'XZ'], [:zip, '10020']]) + # Cassandra::UDT.new([[:street, '123 Main St.'], + # [:city, 'Whatever'], + # [:state, 'XZ'], + # [:zip, '10020']]) def initialize(*values) values = Array(values.first) if values.one? From df32d1e4b143e0a205aec055a5e19fc02c228ce9 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 28 Mar 2016 10:13:09 -0700 Subject: [PATCH 017/196] RUBY-184 - Add unit tests around schema metadata fetching (tables, views, indexes) --- lib/cassandra/column_container.rb | 14 +- lib/cassandra/materialized_view.rb | 6 +- lib/cassandra/table.rb | 1 - .../cluster/schema/fetchers/1.2.19-data.json | 107 + .../cluster/schema/fetchers/1.2.19-schema.cql | 55 + .../cluster/schema/fetchers/2.0.16-data.json | 187 ++ .../cluster/schema/fetchers/2.0.16-schema.cql | 67 + .../cluster/schema/fetchers/2.1.9-data.json | 184 + .../cluster/schema/fetchers/2.1.9-schema.cql | 64 + .../cluster/schema/fetchers/2.2.1-data.json | 178 + .../cluster/schema/fetchers/2.2.1-schema.cql | 64 + .../schema/fetchers/3.0.0-beta2-data.json | 2950 ----------------- .../schema/fetchers/3.0.0-beta2-schema.cql | 760 ----- .../cluster/schema/fetchers/3.0.0-data.json | 402 +++ .../cluster/schema/fetchers/3.0.0-schema.cql | 129 + .../cassandra/cluster/schema/fetchers_spec.rb | 57 +- 16 files changed, 1484 insertions(+), 3741 deletions(-) delete mode 100644 spec/cassandra/cluster/schema/fetchers/3.0.0-beta2-data.json delete mode 100644 spec/cassandra/cluster/schema/fetchers/3.0.0-beta2-schema.cql create mode 100644 spec/cassandra/cluster/schema/fetchers/3.0.0-data.json create mode 100644 spec/cassandra/cluster/schema/fetchers/3.0.0-schema.cql diff --git a/lib/cassandra/column_container.rb b/lib/cassandra/column_container.rb index 39c4e07be..c8ae29269 100644 --- a/lib/cassandra/column_container.rb +++ b/lib/cassandra/column_container.rb @@ -127,16 +127,15 @@ def to_cql options << 'COMPACT STORAGE' if @compact_storage unless @bloom_filter_fp_chance.nil? - options << - "bloom_filter_fp_chance = #{Util.encode_object(@bloom_filter_fp_chance)}" + options << "bloom_filter_fp_chance = #{Util.encode_object(@bloom_filter_fp_chance)}" end options << "caching = #{Util.encode_object(@caching)}" unless @caching.nil? options << "comment = #{Util.encode_object(@comment)}" unless @comment.nil? options << "compaction = #{@compaction_strategy.to_cql}" unless @compaction_strategy.nil? options << "compression = #{Util.encode_object(@compression)}" unless @compression.nil? + options << "crc_check_chance = #{Util.encode_object(@crc_check_chance)}" unless @crc_check_chance.nil? unless @local_read_repair_chance.nil? - options << 'dclocal_read_repair_chance = ' \ - "#{Util.encode_object(@local_read_repair_chance)}" + options << "dclocal_read_repair_chance = #{Util.encode_object(@local_read_repair_chance)}" end unless @default_time_to_live.nil? options << "default_time_to_live = #{Util.encode_object(@default_time_to_live)}" @@ -145,8 +144,7 @@ def to_cql options << "index_interval = #{Util.encode_object(@index_interval)}" unless @index_interval.nil? options << "max_index_interval = #{Util.encode_object(@max_index_interval)}" unless @max_index_interval.nil? unless @memtable_flush_period_in_ms.nil? - options << 'memtable_flush_period_in_ms = ' \ - "#{Util.encode_object(@memtable_flush_period_in_ms)}" + options << "memtable_flush_period_in_ms = #{Util.encode_object(@memtable_flush_period_in_ms)}" end options << "min_index_interval = #{Util.encode_object(@min_index_interval)}" unless @min_index_interval.nil? unless @populate_io_cache_on_flush.nil? @@ -155,10 +153,6 @@ def to_cql options << "read_repair_chance = #{Util.encode_object(@read_repair_chance)}" unless @read_repair_chance.nil? options << "replicate_on_write = '#{@replicate_on_write}'" unless @replicate_on_write.nil? options << "speculative_retry = #{Util.encode_object(@speculative_retry)}" unless @speculative_retry.nil? - unless @crc_check_chance.nil? - options << 'crc_check_chance = ' \ - "#{Util.encode_object(@crc_check_chance)}" - end options.join("\nAND ") end diff --git a/lib/cassandra/materialized_view.rb b/lib/cassandra/materialized_view.rb index e47851f39..b0330954b 100644 --- a/lib/cassandra/materialized_view.rb +++ b/lib/cassandra/materialized_view.rb @@ -54,18 +54,18 @@ def to_cql end cql << "\nFROM #{keyspace_name}.#{Util.escape_name(@base_table.name)}" cql << "\nWHERE #{@where_clause}" if @where_clause - cql << "\nPRIMARY KEY((" + cql << "\nPRIMARY KEY ((" cql << @partition_key.map do |column| Util.escape_name(column.name) end.join(', ') cql << ')' unless @clustering_columns.empty? - cql << ',' + cql << ', ' cql << @clustering_columns.map do |column| Util.escape_name(column.name) end.join(', ') end - cql << ")\nWITH #{@options.to_cql};" + cql << ")\nWITH #{@options.to_cql.split("\n").join("\n ")};" end # @private diff --git a/lib/cassandra/table.rb b/lib/cassandra/table.rb index 79370ece9..3fabc8fdf 100644 --- a/lib/cassandra/table.rb +++ b/lib/cassandra/table.rb @@ -71,7 +71,6 @@ def each_index(&block) # @return [String] a cql representation of this table def to_cql cql = "CREATE TABLE #{Util.escape_name(@keyspace.name)}.#{Util.escape_name(@name)} (\n" - primary_key = nil primary_key = @partition_key.first.name if @partition_key.one? && @clustering_columns.empty? first = true diff --git a/spec/cassandra/cluster/schema/fetchers/1.2.19-data.json b/spec/cassandra/cluster/schema/fetchers/1.2.19-data.json index f8ddcd849..fa742db2b 100644 --- a/spec/cassandra/cluster/schema/fetchers/1.2.19-data.json +++ b/spec/cassandra/cluster/schema/fetchers/1.2.19-data.json @@ -11,6 +11,12 @@ "durable_writes": true, "strategy_class": "org.apache.cassandra.locator.SimpleStrategy", "strategy_options": "{\"replication_factor\":\"2\"}" + }, + { + "keyspace_name": "simplex", + "durable_writes": true, + "strategy_class": "org.apache.cassandra.locator.SimpleStrategy", + "strategy_options": "{\"replication_factor\":\"1\"}" } ], "SELECT * FROM system.schema_columnfamilies": [ @@ -472,6 +478,87 @@ "subcomparator": null, "type": "Standard", "value_alias": null + }, + { + "keyspace_name": "simplex", + "columnfamily_name": "t1", + "bloom_filter_fp_chance": 0.01, + "caching": "KEYS_ONLY", + "column_aliases": "[\"f2\"]", + "comment": "test table", + "compaction_strategy_class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", + "compaction_strategy_options": "{}", + "comparator": "org.apache.cassandra.db.marshal.CompositeType(org.apache.cassandra.db.marshal.Int32Type,org.apache.cassandra.db.marshal.UTF8Type)", + "compression_parameters": "{\"sstable_compression\":\"org.apache.cassandra.io.compress.LZ4Compressor\"}", + "default_validator": "org.apache.cassandra.db.marshal.BytesType", + "gc_grace_seconds": 2, + "id": null, + "key_alias": null, + "key_aliases": "[\"f1\"]", + "key_validator": "org.apache.cassandra.db.marshal.Int32Type", + "local_read_repair_chance": 0.1, + "max_compaction_threshold": 32, + "min_compaction_threshold": 4, + "populate_io_cache_on_flush": false, + "read_repair_chance": 0.1, + "replicate_on_write": true, + "subcomparator": null, + "type": "Standard", + "value_alias": null + }, + { + "keyspace_name": "simplex", + "columnfamily_name": "t2", + "bloom_filter_fp_chance": 0.01, + "caching": "KEYS_ONLY", + "column_aliases": "[\"f2\"]", + "comment": "compact test table", + "compaction_strategy_class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", + "compaction_strategy_options": "{}", + "comparator": "org.apache.cassandra.db.marshal.ReversedType(org.apache.cassandra.db.marshal.Int32Type)", + "compression_parameters": "{\"sstable_compression\":\"org.apache.cassandra.io.compress.LZ4Compressor\"}", + "default_validator": "org.apache.cassandra.db.marshal.Int32Type", + "gc_grace_seconds": 2, + "id": null, + "key_alias": null, + "key_aliases": "[\"f1\"]", + "key_validator": "org.apache.cassandra.db.marshal.Int32Type", + "local_read_repair_chance": 0.1, + "max_compaction_threshold": 32, + "min_compaction_threshold": 4, + "populate_io_cache_on_flush": false, + "read_repair_chance": 0.1, + "replicate_on_write": true, + "subcomparator": null, + "type": "Standard", + "value_alias": "f3" + }, + { + "keyspace_name": "simplex", + "columnfamily_name": "t3", + "bloom_filter_fp_chance": 0.01, + "caching": "KEYS_ONLY", + "column_aliases": "[]", + "comment": "test table one column key", + "compaction_strategy_class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", + "compaction_strategy_options": "{}", + "comparator": "org.apache.cassandra.db.marshal.CompositeType(org.apache.cassandra.db.marshal.UTF8Type)", + "compression_parameters": "{\"sstable_compression\":\"org.apache.cassandra.io.compress.LZ4Compressor\"}", + "default_validator": "org.apache.cassandra.db.marshal.BytesType", + "gc_grace_seconds": 2, + "id": null, + "key_alias": null, + "key_aliases": "[\"f1\"]", + "key_validator": "org.apache.cassandra.db.marshal.Int32Type", + "local_read_repair_chance": 0.1, + "max_compaction_threshold": 32, + "min_compaction_threshold": 4, + "populate_io_cache_on_flush": false, + "read_repair_chance": 0.1, + "replicate_on_write": true, + "subcomparator": null, + "type": "Standard", + "value_alias": null } ], "SELECT * FROM system.schema_columns": [ @@ -1114,6 +1201,26 @@ "index_options": null, "index_type": null, "validator": "org.apache.cassandra.db.marshal.DateType" + }, + { + "keyspace_name": "simplex", + "columnfamily_name": "t1", + "column_name": "f3", + "component_index": 1, + "index_name": "ind1", + "index_options": "{\"prefix_size\":\"1\"}", + "index_type": "COMPOSITES", + "validator": "org.apache.cassandra.db.marshal.Int32Type" + }, + { + "keyspace_name": "simplex", + "columnfamily_name": "t3", + "column_name": "f2", + "component_index": 0, + "index_name": null, + "index_options": "null", + "index_type": null, + "validator": "org.apache.cassandra.db.marshal.Int32Type" } ] } \ No newline at end of file diff --git a/spec/cassandra/cluster/schema/fetchers/1.2.19-schema.cql b/spec/cassandra/cluster/schema/fetchers/1.2.19-schema.cql index c5e66c324..f435a80f9 100644 --- a/spec/cassandra/cluster/schema/fetchers/1.2.19-schema.cql +++ b/spec/cassandra/cluster/schema/fetchers/1.2.19-schema.cql @@ -337,4 +337,59 @@ WITH bloom_filter_fp_chance = 0.01 AND gc_grace_seconds = 0 AND populate_io_cache_on_flush = 'false' AND read_repair_chance = 0.0 + AND replicate_on_write = 'true'; + +CREATE KEYSPACE simplex WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'} AND durable_writes = true; + +CREATE TABLE simplex."t1" ( + f1 int, + f2 int, + f3 int, + PRIMARY KEY (f1, f2) +) +WITH bloom_filter_fp_chance = 0.01 + AND caching = 'KEYS_ONLY' + AND comment = 'test table' + AND compaction = {'class': 'SizeTieredCompactionStrategy'} + AND compression = {'sstable_compression': 'LZ4Compressor'} + AND dclocal_read_repair_chance = 0.1 + AND gc_grace_seconds = 2 + AND populate_io_cache_on_flush = 'false' + AND read_repair_chance = 0.1 + AND replicate_on_write = 'true'; + +CREATE INDEX "ind1" ON simplex."t1" ("f3"); + +CREATE TABLE simplex."t2" ( + f1 int, + f2 int, + f3 int, + PRIMARY KEY (f1, f2) +) +WITH CLUSTERING ORDER BY (f2 DESC) + AND COMPACT STORAGE + AND bloom_filter_fp_chance = 0.01 + AND caching = 'KEYS_ONLY' + AND comment = 'compact test table' + AND compaction = {'class': 'SizeTieredCompactionStrategy'} + AND compression = {'sstable_compression': 'LZ4Compressor'} + AND dclocal_read_repair_chance = 0.1 + AND gc_grace_seconds = 2 + AND populate_io_cache_on_flush = 'false' + AND read_repair_chance = 0.1 + AND replicate_on_write = 'true'; + +CREATE TABLE simplex."t3" ( + f1 int PRIMARY KEY, + f2 int +) +WITH bloom_filter_fp_chance = 0.01 + AND caching = 'KEYS_ONLY' + AND comment = 'test table one column key' + AND compaction = {'class': 'SizeTieredCompactionStrategy'} + AND compression = {'sstable_compression': 'LZ4Compressor'} + AND dclocal_read_repair_chance = 0.1 + AND gc_grace_seconds = 2 + AND populate_io_cache_on_flush = 'false' + AND read_repair_chance = 0.1 AND replicate_on_write = 'true'; \ No newline at end of file diff --git a/spec/cassandra/cluster/schema/fetchers/2.0.16-data.json b/spec/cassandra/cluster/schema/fetchers/2.0.16-data.json index 201e99b69..2905afb46 100644 --- a/spec/cassandra/cluster/schema/fetchers/2.0.16-data.json +++ b/spec/cassandra/cluster/schema/fetchers/2.0.16-data.json @@ -11,6 +11,12 @@ "durable_writes": true, "strategy_class": "org.apache.cassandra.locator.SimpleStrategy", "strategy_options": "{\"replication_factor\":\"2\"}" + }, + { + "keyspace_name": "simplex", + "durable_writes": true, + "strategy_class": "org.apache.cassandra.locator.SimpleStrategy", + "strategy_options": "{\"replication_factor\":\"1\"}" } ], "SELECT * FROM system.schema_columnfamilies": [ @@ -571,6 +577,99 @@ "subcomparator": null, "type": "Standard", "value_alias": null + }, + { + "keyspace_name": "simplex", + "columnfamily_name": "t1", + "bloom_filter_fp_chance": 0.01, + "caching": "KEYS_ONLY", + "column_aliases": "[f2]", + "comment": "test table", + "compaction_strategy_class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", + "compaction_strategy_options": "{}", + "comparator": "org.apache.cassandra.db.marshal.CompositeType(org.apache.cassandra.db.marshal.Int32Type,org.apache.cassandra.db.marshal.UTF8Type)", + "compression_parameters": "{\"sstable_compression\":\"org.apache.cassandra.io.compress.LZ4Compressor\"}", + "default_time_to_live": 1, + "default_validator": "org.apache.cassandra.db.marshal.BytesType", + "dropped_columns": null, + "gc_grace_seconds": 2, + "index_interval": 128, + "is_dense": false, + "key_aliases": "[\"f1\"]", + "key_validator": "org.apache.cassandra.db.marshal.Int32Type", + "local_read_repair_chance": 0.1, + "max_compaction_threshold": 32, + "memtable_flush_period_in_ms": 3600000, + "min_compaction_threshold": 4, + "populate_io_cache_on_flush": false, + "read_repair_chance": 0.1, + "replicate_on_write": true, + "speculative_retry": "99.0PERCENTILE", + "subcomparator": null, + "type": "Standard", + "value_alias": null + }, + { + "keyspace_name": "simplex", + "columnfamily_name": "t2", + "bloom_filter_fp_chance": 0.01, + "caching": "KEYS_ONLY", + "column_aliases": "[f2]", + "comment": "compact test table", + "compaction_strategy_class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", + "compaction_strategy_options": "{}", + "comparator": "org.apache.cassandra.db.marshal.Int32Type", + "compression_parameters": "{\"sstable_compression\":\"org.apache.cassandra.io.compress.LZ4Compressor\"}", + "default_time_to_live": 1, + "default_validator": "org.apache.cassandra.db.marshal.BytesType", + "dropped_columns": null, + "gc_grace_seconds": 2, + "index_interval": 128, + "is_dense": false, + "key_aliases": "[\"f1\"]", + "key_validator": "org.apache.cassandra.db.marshal.Int32Type", + "local_read_repair_chance": 0.1, + "max_compaction_threshold": 32, + "memtable_flush_period_in_ms": 3600000, + "min_compaction_threshold": 4, + "populate_io_cache_on_flush": false, + "read_repair_chance": 0.1, + "replicate_on_write": true, + "speculative_retry": "99.0PERCENTILE", + "subcomparator": null, + "type": "Standard", + "value_alias": null + }, + { + "keyspace_name": "simplex", + "columnfamily_name": "t3", + "bloom_filter_fp_chance": 0.01, + "caching": "KEYS_ONLY", + "column_aliases": "[]", + "comment": "test table one column key", + "compaction_strategy_class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", + "compaction_strategy_options": "{}", + "comparator": "org.apache.cassandra.db.marshal.CompositeType(org.apache.cassandra.db.marshal.UTF8Type)", + "compression_parameters": "{\"sstable_compression\":\"org.apache.cassandra.io.compress.LZ4Compressor\"}", + "default_time_to_live": 1, + "default_validator": "org.apache.cassandra.db.marshal.BytesType", + "dropped_columns": null, + "gc_grace_seconds": 2, + "index_interval": 128, + "is_dense": false, + "key_aliases": "[\"f1\"]", + "key_validator": "org.apache.cassandra.db.marshal.Int32Type", + "local_read_repair_chance": 0.1, + "max_compaction_threshold": 32, + "memtable_flush_period_in_ms": 3600000, + "min_compaction_threshold": 4, + "populate_io_cache_on_flush": false, + "read_repair_chance": 0.1, + "replicate_on_write": true, + "speculative_retry": "99.0PERCENTILE", + "subcomparator": null, + "type": "Standard", + "value_alias": null } ], "SELECT * FROM system.schema_columns": [ @@ -1948,6 +2047,94 @@ "index_type": null, "type": "regular", "validator": "org.apache.cassandra.db.marshal.TimestampType" + }, + { + "keyspace_name": "simplex", + "columnfamily_name": "t1", + "column_name": "f1", + "component_index": null, + "index_name": null, + "index_options": null, + "index_type": null, + "type": "partition_key", + "validator": "org.apache.cassandra.db.marshal.Int32Type" + }, + { + "keyspace_name": "simplex", + "columnfamily_name": "t1", + "column_name": "f2", + "component_index": 0, + "index_name": "ind1", + "index_options": "{}", + "index_type": "COMPOSITES", + "type": "clustering_key", + "validator": "org.apache.cassandra.db.marshal.Int32Type" + }, + { + "keyspace_name": "simplex", + "columnfamily_name": "t1", + "column_name": "f3", + "component_index": 1, + "index_name": null, + "index_options": null, + "index_type": null, + "type": "regular", + "validator": "org.apache.cassandra.db.marshal.Int32Type" + }, + { + "keyspace_name": "simplex", + "columnfamily_name": "t2", + "column_name": "f1", + "component_index": null, + "index_name": null, + "index_options": "null", + "index_type": null, + "type": "partition_key", + "validator": "org.apache.cassandra.db.marshal.Int32Type" + }, + { + "keyspace_name": "simplex", + "columnfamily_name": "t2", + "column_name": "f2", + "component_index": 0, + "index_name": null, + "index_options": "null", + "index_type": null, + "type": "clustering_key", + "validator": "org.apache.cassandra.db.marshal.ReversedType(org.apache.cassandra.db.marshal.Int32Type)" + }, + { + "keyspace_name": "simplex", + "columnfamily_name": "t2", + "column_name": "f3", + "component_index": 1, + "index_name": null, + "index_options": null, + "index_type": null, + "type": "regular", + "validator": "org.apache.cassandra.db.marshal.Int32Type" + }, + { + "keyspace_name": "simplex", + "columnfamily_name": "t3", + "column_name": "f1", + "component_index": null, + "index_name": null, + "index_options": "null", + "index_type": null, + "type": "partition_key", + "validator": "org.apache.cassandra.db.marshal.Int32Type" + }, + { + "keyspace_name": "simplex", + "columnfamily_name": "t3", + "column_name": "f2", + "component_index": 0, + "index_name": null, + "index_options": "null", + "index_type": null, + "type": "regular", + "validator": "org.apache.cassandra.db.marshal.Int32Type" } ] } \ No newline at end of file diff --git a/spec/cassandra/cluster/schema/fetchers/2.0.16-schema.cql b/spec/cassandra/cluster/schema/fetchers/2.0.16-schema.cql index 71ef02c6a..58628064a 100644 --- a/spec/cassandra/cluster/schema/fetchers/2.0.16-schema.cql +++ b/spec/cassandra/cluster/schema/fetchers/2.0.16-schema.cql @@ -442,4 +442,71 @@ WITH bloom_filter_fp_chance = 0.01 AND populate_io_cache_on_flush = 'false' AND read_repair_chance = 0.0 AND replicate_on_write = 'true' + AND speculative_retry = '99.0PERCENTILE'; + +CREATE KEYSPACE simplex WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'} AND durable_writes = true; + +CREATE TABLE simplex."t1" ( + f1 int, + f2 int, + f3 int, + PRIMARY KEY (f1, f2) +) +WITH bloom_filter_fp_chance = 0.01 + AND caching = 'KEYS_ONLY' + AND comment = 'test table' + AND compaction = {'class': 'SizeTieredCompactionStrategy'} + AND compression = {'sstable_compression': 'LZ4Compressor'} + AND dclocal_read_repair_chance = 0.1 + AND default_time_to_live = 1 + AND gc_grace_seconds = 2 + AND index_interval = 128 + AND memtable_flush_period_in_ms = 3600000 + AND populate_io_cache_on_flush = 'false' + AND read_repair_chance = 0.1 + AND replicate_on_write = 'true' + AND speculative_retry = '99.0PERCENTILE'; + +CREATE INDEX "ind1" ON simplex."t1" ("f2"); + +CREATE TABLE simplex."t2" ( + f1 int, + f2 int, + f3 int, + PRIMARY KEY (f1, f2) +) +WITH CLUSTERING ORDER BY (f2 DESC) + AND COMPACT STORAGE + AND bloom_filter_fp_chance = 0.01 + AND caching = 'KEYS_ONLY' + AND comment = 'compact test table' + AND compaction = {'class': 'SizeTieredCompactionStrategy'} + AND compression = {'sstable_compression': 'LZ4Compressor'} + AND dclocal_read_repair_chance = 0.1 + AND default_time_to_live = 1 + AND gc_grace_seconds = 2 + AND index_interval = 128 + AND memtable_flush_period_in_ms = 3600000 + AND populate_io_cache_on_flush = 'false' + AND read_repair_chance = 0.1 + AND replicate_on_write = 'true' + AND speculative_retry = '99.0PERCENTILE'; + +CREATE TABLE simplex."t3" ( + f1 int PRIMARY KEY, + f2 int +) +WITH bloom_filter_fp_chance = 0.01 + AND caching = 'KEYS_ONLY' + AND comment = 'test table one column key' + AND compaction = {'class': 'SizeTieredCompactionStrategy'} + AND compression = {'sstable_compression': 'LZ4Compressor'} + AND dclocal_read_repair_chance = 0.1 + AND default_time_to_live = 1 + AND gc_grace_seconds = 2 + AND index_interval = 128 + AND memtable_flush_period_in_ms = 3600000 + AND populate_io_cache_on_flush = 'false' + AND read_repair_chance = 0.1 + AND replicate_on_write = 'true' AND speculative_retry = '99.0PERCENTILE'; \ No newline at end of file diff --git a/spec/cassandra/cluster/schema/fetchers/2.1.9-data.json b/spec/cassandra/cluster/schema/fetchers/2.1.9-data.json index cc279f48f..8d89b95f7 100644 --- a/spec/cassandra/cluster/schema/fetchers/2.1.9-data.json +++ b/spec/cassandra/cluster/schema/fetchers/2.1.9-data.json @@ -11,6 +11,12 @@ "durable_writes": true, "strategy_class": "org.apache.cassandra.locator.SimpleStrategy", "strategy_options": "{\"replication_factor\":\"2\"}" + }, + { + "keyspace_name": "simplex", + "durable_writes": true, + "strategy_class": "org.apache.cassandra.locator.SimpleStrategy", + "strategy_options": "{\"replication_factor\":\"1\"}" } ], "SELECT * FROM system.schema_columnfamilies": [ @@ -621,6 +627,96 @@ "subcomparator": null, "type": "Standard", "value_alias": null + }, + { + "keyspace_name": "simplex", + "columnfamily_name": "t1", + "bloom_filter_fp_chance": 0.01, + "caching": "{\"keys\":\"ALL\", \"rows_per_partition\":\"NONE\"}", + "cf_id": "c5e99f16-8677-3914-b17e-960613512345", + "comment": "test table", + "compaction_strategy_class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", + "compaction_strategy_options": "{}", + "comparator": "org.apache.cassandra.db.marshal.CompositeType(org.apache.cassandra.db.marshal.Int32Type,org.apache.cassandra.db.marshal.UTF8Type)", + "compression_parameters": "{\"sstable_compression\":\"org.apache.cassandra.io.compress.LZ4Compressor\"}", + "default_time_to_live": 1, + "default_validator": "org.apache.cassandra.db.marshal.BytesType", + "dropped_columns": null, + "gc_grace_seconds": 2, + "is_dense": false, + "key_aliases": "[\"f1\"]", + "key_validator": "org.apache.cassandra.db.marshal.Int32Type", + "local_read_repair_chance": 0.1, + "max_compaction_threshold": 32, + "max_index_interval": 2048, + "memtable_flush_period_in_ms": 3600000, + "min_compaction_threshold": 4, + "min_index_interval": 128, + "read_repair_chance": 0.1, + "speculative_retry": "99.0PERCENTILE", + "subcomparator": null, + "type": "Standard", + "value_alias": null + }, + { + "keyspace_name": "simplex", + "columnfamily_name": "t2", + "bloom_filter_fp_chance": 0.01, + "caching": "{\"keys\":\"ALL\", \"rows_per_partition\":\"NONE\"}", + "cf_id": "c5e99f16-8677-3914-b17e-960613512345", + "comment": "compact test table", + "compaction_strategy_class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", + "compaction_strategy_options": "{}", + "comparator": "org.apache.cassandra.db.marshal.Int32Type", + "compression_parameters": "{\"sstable_compression\":\"org.apache.cassandra.io.compress.LZ4Compressor\"}", + "default_time_to_live": 1, + "default_validator": "org.apache.cassandra.db.marshal.BytesType", + "dropped_columns": null, + "gc_grace_seconds": 2, + "is_dense": false, + "key_aliases": "[\"f1\"]", + "key_validator": "org.apache.cassandra.db.marshal.Int32Type", + "local_read_repair_chance": 0.1, + "max_compaction_threshold": 32, + "max_index_interval": 2048, + "memtable_flush_period_in_ms": 3600000, + "min_compaction_threshold": 4, + "min_index_interval": 128, + "read_repair_chance": 0.1, + "speculative_retry": "99.0PERCENTILE", + "subcomparator": null, + "type": "Standard", + "value_alias": null + }, + { + "keyspace_name": "simplex", + "columnfamily_name": "t3", + "bloom_filter_fp_chance": 0.01, + "caching": "{\"keys\":\"ALL\", \"rows_per_partition\":\"NONE\"}", + "cf_id": "c5e99f16-8677-3914-b17e-960613512345", + "comment": "test table one column key", + "compaction_strategy_class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", + "compaction_strategy_options": "{}", + "comparator": "org.apache.cassandra.db.marshal.CompositeType(org.apache.cassandra.db.marshal.UTF8Type)", + "compression_parameters": "{\"sstable_compression\":\"org.apache.cassandra.io.compress.LZ4Compressor\"}", + "default_time_to_live": 1, + "default_validator": "org.apache.cassandra.db.marshal.BytesType", + "dropped_columns": null, + "gc_grace_seconds": 2, + "is_dense": false, + "key_aliases": "[\"f1\"]", + "key_validator": "org.apache.cassandra.db.marshal.Int32Type", + "local_read_repair_chance": 0.1, + "max_compaction_threshold": 32, + "max_index_interval": 2048, + "memtable_flush_period_in_ms": 3600000, + "min_compaction_threshold": 4, + "min_index_interval": 128, + "read_repair_chance": 0.1, + "speculative_retry": "99.0PERCENTILE", + "subcomparator": null, + "type": "Standard", + "value_alias": null } ], "SELECT * FROM system.schema_columns": [ @@ -2097,6 +2193,94 @@ "index_type": null, "type": "regular", "validator": "org.apache.cassandra.db.marshal.TimestampType" + }, + { + "keyspace_name": "simplex", + "columnfamily_name": "t1", + "column_name": "f1", + "component_index": null, + "index_name": null, + "index_options": "null", + "index_type": null, + "type": "partition_key", + "validator": "org.apache.cassandra.db.marshal.Int32Type" + }, + { + "keyspace_name": "simplex", + "columnfamily_name": "t1", + "column_name": "f2", + "component_index": 0, + "index_name": "ind1", + "index_options": "{}", + "index_type": "COMPOSITES", + "type": "clustering_key", + "validator": "org.apache.cassandra.db.marshal.Int32Type" + }, + { + "keyspace_name": "simplex", + "columnfamily_name": "t1", + "column_name": "f3", + "component_index": 1, + "index_name": null, + "index_options": null, + "index_type": null, + "type": "regular", + "validator": "org.apache.cassandra.db.marshal.Int32Type" + }, + { + "keyspace_name": "simplex", + "columnfamily_name": "t2", + "column_name": "f1", + "component_index": null, + "index_name": null, + "index_options": "null", + "index_type": null, + "type": "partition_key", + "validator": "org.apache.cassandra.db.marshal.Int32Type" + }, + { + "keyspace_name": "simplex", + "columnfamily_name": "t2", + "column_name": "f2", + "component_index": 0, + "index_name": null, + "index_options": "null", + "index_type": null, + "type": "clustering_key", + "validator": "org.apache.cassandra.db.marshal.ReversedType(org.apache.cassandra.db.marshal.Int32Type)" + }, + { + "keyspace_name": "simplex", + "columnfamily_name": "t2", + "column_name": "f3", + "component_index": 1, + "index_name": null, + "index_options": null, + "index_type": null, + "type": "regular", + "validator": "org.apache.cassandra.db.marshal.Int32Type" + }, + { + "keyspace_name": "simplex", + "columnfamily_name": "t3", + "column_name": "f1", + "component_index": null, + "index_name": null, + "index_options": "null", + "index_type": null, + "type": "partition_key", + "validator": "org.apache.cassandra.db.marshal.Int32Type" + }, + { + "keyspace_name": "simplex", + "columnfamily_name": "t3", + "column_name": "f2", + "component_index": 0, + "index_name": null, + "index_options": "null", + "index_type": null, + "type": "regular", + "validator": "org.apache.cassandra.db.marshal.Int32Type" } ], "SELECT * FROM system.schema_usertypes": [ diff --git a/spec/cassandra/cluster/schema/fetchers/2.1.9-schema.cql b/spec/cassandra/cluster/schema/fetchers/2.1.9-schema.cql index 970bf5af3..c4907c6bc 100644 --- a/spec/cassandra/cluster/schema/fetchers/2.1.9-schema.cql +++ b/spec/cassandra/cluster/schema/fetchers/2.1.9-schema.cql @@ -450,4 +450,68 @@ WITH bloom_filter_fp_chance = 0.01 AND memtable_flush_period_in_ms = 3600000 AND min_index_interval = 128 AND read_repair_chance = 0.0 + AND speculative_retry = '99.0PERCENTILE'; + +CREATE KEYSPACE simplex WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'} AND durable_writes = true; + +CREATE TABLE simplex."t1" ( + f1 int, + f2 int, + f3 int, + PRIMARY KEY (f1, f2) +) +WITH bloom_filter_fp_chance = 0.01 + AND caching = '{"keys":"ALL", "rows_per_partition":"NONE"}' + AND comment = 'test table' + AND compaction = {'class': 'SizeTieredCompactionStrategy'} + AND compression = {'sstable_compression': 'LZ4Compressor'} + AND dclocal_read_repair_chance = 0.1 + AND default_time_to_live = 1 + AND gc_grace_seconds = 2 + AND max_index_interval = 2048 + AND memtable_flush_period_in_ms = 3600000 + AND min_index_interval = 128 + AND read_repair_chance = 0.1 + AND speculative_retry = '99.0PERCENTILE'; + +CREATE INDEX "ind1" ON simplex."t1" ("f2"); + +CREATE TABLE simplex."t2" ( + f1 int, + f2 int, + f3 int, + PRIMARY KEY (f1, f2) +) +WITH CLUSTERING ORDER BY (f2 DESC) + AND COMPACT STORAGE + AND bloom_filter_fp_chance = 0.01 + AND caching = '{"keys":"ALL", "rows_per_partition":"NONE"}' + AND comment = 'compact test table' + AND compaction = {'class': 'SizeTieredCompactionStrategy'} + AND compression = {'sstable_compression': 'LZ4Compressor'} + AND dclocal_read_repair_chance = 0.1 + AND default_time_to_live = 1 + AND gc_grace_seconds = 2 + AND max_index_interval = 2048 + AND memtable_flush_period_in_ms = 3600000 + AND min_index_interval = 128 + AND read_repair_chance = 0.1 + AND speculative_retry = '99.0PERCENTILE'; + +CREATE TABLE simplex."t3" ( + f1 int PRIMARY KEY, + f2 int +) +WITH bloom_filter_fp_chance = 0.01 + AND caching = '{"keys":"ALL", "rows_per_partition":"NONE"}' + AND comment = 'test table one column key' + AND compaction = {'class': 'SizeTieredCompactionStrategy'} + AND compression = {'sstable_compression': 'LZ4Compressor'} + AND dclocal_read_repair_chance = 0.1 + AND default_time_to_live = 1 + AND gc_grace_seconds = 2 + AND max_index_interval = 2048 + AND memtable_flush_period_in_ms = 3600000 + AND min_index_interval = 128 + AND read_repair_chance = 0.1 AND speculative_retry = '99.0PERCENTILE'; \ No newline at end of file diff --git a/spec/cassandra/cluster/schema/fetchers/2.2.1-data.json b/spec/cassandra/cluster/schema/fetchers/2.2.1-data.json index 5111eb146..593634d04 100644 --- a/spec/cassandra/cluster/schema/fetchers/2.2.1-data.json +++ b/spec/cassandra/cluster/schema/fetchers/2.2.1-data.json @@ -23,6 +23,12 @@ "durable_writes": true, "strategy_class": "org.apache.cassandra.locator.SimpleStrategy", "strategy_options": "{\"replication_factor\":\"2\"}" + }, + { + "keyspace_name": "simplex", + "durable_writes": true, + "strategy_class": "org.apache.cassandra.locator.SimpleStrategy", + "strategy_options": "{\"replication_factor\":\"1\"}" } ], "SELECT * FROM system.schema_columnfamilies": [ @@ -809,6 +815,90 @@ "speculative_retry": "99.0PERCENTILE", "subcomparator": null, "type": "Standard" + }, + { + "keyspace_name": "simplex", + "columnfamily_name": "t1", + "bloom_filter_fp_chance": 0.01, + "caching": "{\"keys\":\"ALL\", \"rows_per_partition\":\"NONE\"}", + "cf_id": "c5e99f16-8677-3914-b17e-960613512345", + "comment": "test table", + "compaction_strategy_class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", + "compaction_strategy_options": "{}", + "comparator": "org.apache.cassandra.db.marshal.CompositeType(org.apache.cassandra.db.marshal.Int32Type,org.apache.cassandra.db.marshal.UTF8Type)", + "compression_parameters": "{\"sstable_compression\":\"org.apache.cassandra.io.compress.LZ4Compressor\"}", + "default_time_to_live": 1, + "default_validator": "org.apache.cassandra.db.marshal.BytesType", + "dropped_columns": null, + "gc_grace_seconds": 2, + "is_dense": false, + "key_validator": "org.apache.cassandra.db.marshal.Int32Type", + "local_read_repair_chance": 0.1, + "max_compaction_threshold": 32, + "max_index_interval": 2048, + "memtable_flush_period_in_ms": 3600000, + "min_compaction_threshold": 4, + "min_index_interval": 128, + "read_repair_chance": 0.1, + "speculative_retry": "99.0PERCENTILE", + "subcomparator": null, + "type": "Standard" + }, + { + "keyspace_name": "simplex", + "columnfamily_name": "t2", + "bloom_filter_fp_chance": 0.01, + "caching": "{\"keys\":\"ALL\", \"rows_per_partition\":\"NONE\"}", + "cf_id": "c5e99f16-8677-3914-b17e-960613512345", + "comment": "compact test table", + "compaction_strategy_class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", + "compaction_strategy_options": "{}", + "comparator": "org.apache.cassandra.db.marshal.Int32Type", + "compression_parameters": "{\"sstable_compression\":\"org.apache.cassandra.io.compress.LZ4Compressor\"}", + "default_time_to_live": 1, + "default_validator": "org.apache.cassandra.db.marshal.BytesType", + "dropped_columns": null, + "gc_grace_seconds": 2, + "is_dense": false, + "key_validator": "org.apache.cassandra.db.marshal.Int32Type", + "local_read_repair_chance": 0.1, + "max_compaction_threshold": 32, + "max_index_interval": 2048, + "memtable_flush_period_in_ms": 3600000, + "min_compaction_threshold": 4, + "min_index_interval": 128, + "read_repair_chance": 0.1, + "speculative_retry": "99.0PERCENTILE", + "subcomparator": null, + "type": "Standard" + }, + { + "keyspace_name": "simplex", + "columnfamily_name": "t3", + "bloom_filter_fp_chance": 0.01, + "caching": "{\"keys\":\"ALL\", \"rows_per_partition\":\"NONE\"}", + "cf_id": "c5e99f16-8677-3914-b17e-960613512345", + "comment": "test table one column key", + "compaction_strategy_class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", + "compaction_strategy_options": "{}", + "comparator": "org.apache.cassandra.db.marshal.CompositeType(org.apache.cassandra.db.marshal.UTF8Type)", + "compression_parameters": "{\"sstable_compression\":\"org.apache.cassandra.io.compress.LZ4Compressor\"}", + "default_time_to_live": 1, + "default_validator": "org.apache.cassandra.db.marshal.BytesType", + "dropped_columns": null, + "gc_grace_seconds": 2, + "is_dense": false, + "key_validator": "org.apache.cassandra.db.marshal.Int32Type", + "local_read_repair_chance": 0.1, + "max_compaction_threshold": 32, + "max_index_interval": 2048, + "memtable_flush_period_in_ms": 3600000, + "min_compaction_threshold": 4, + "min_index_interval": 128, + "read_repair_chance": 0.1, + "speculative_retry": "99.0PERCENTILE", + "subcomparator": null, + "type": "Standard" } ], "SELECT * FROM system.schema_columns": [ @@ -2857,6 +2947,94 @@ "index_type": null, "type": "regular", "validator": "org.apache.cassandra.db.marshal.TimestampType" + }, + { + "keyspace_name": "simplex", + "columnfamily_name": "t1", + "column_name": "f1", + "component_index": null, + "index_name": null, + "index_options": "null", + "index_type": null, + "type": "partition_key", + "validator": "org.apache.cassandra.db.marshal.Int32Type" + }, + { + "keyspace_name": "simplex", + "columnfamily_name": "t1", + "column_name": "f2", + "component_index": 0, + "index_name": "ind1", + "index_options": "{}", + "index_type": "COMPOSITES", + "type": "clustering_key", + "validator": "org.apache.cassandra.db.marshal.Int32Type" + }, + { + "keyspace_name": "simplex", + "columnfamily_name": "t1", + "column_name": "f3", + "component_index": 1, + "index_name": null, + "index_options": null, + "index_type": null, + "type": "regular", + "validator": "org.apache.cassandra.db.marshal.Int32Type" + }, + { + "keyspace_name": "simplex", + "columnfamily_name": "t2", + "column_name": "f1", + "component_index": null, + "index_name": null, + "index_options": "null", + "index_type": null, + "type": "partition_key", + "validator": "org.apache.cassandra.db.marshal.Int32Type" + }, + { + "keyspace_name": "simplex", + "columnfamily_name": "t2", + "column_name": "f2", + "component_index": 0, + "index_name": null, + "index_options": "null", + "index_type": null, + "type": "clustering_key", + "validator": "org.apache.cassandra.db.marshal.ReversedType(org.apache.cassandra.db.marshal.Int32Type)" + }, + { + "keyspace_name": "simplex", + "columnfamily_name": "t2", + "column_name": "f3", + "component_index": 1, + "index_name": null, + "index_options": null, + "index_type": null, + "type": "regular", + "validator": "org.apache.cassandra.db.marshal.Int32Type" + }, + { + "keyspace_name": "simplex", + "columnfamily_name": "t3", + "column_name": "f1", + "component_index": null, + "index_name": null, + "index_options": "null", + "index_type": null, + "type": "partition_key", + "validator": "org.apache.cassandra.db.marshal.Int32Type" + }, + { + "keyspace_name": "simplex", + "columnfamily_name": "t3", + "column_name": "f2", + "component_index": 0, + "index_name": null, + "index_options": "null", + "index_type": null, + "type": "regular", + "validator": "org.apache.cassandra.db.marshal.Int32Type" } ], "SELECT * FROM system.schema_usertypes": [ diff --git a/spec/cassandra/cluster/schema/fetchers/2.2.1-schema.cql b/spec/cassandra/cluster/schema/fetchers/2.2.1-schema.cql index 496331311..8d2262346 100644 --- a/spec/cassandra/cluster/schema/fetchers/2.2.1-schema.cql +++ b/spec/cassandra/cluster/schema/fetchers/2.2.1-schema.cql @@ -656,4 +656,68 @@ WITH bloom_filter_fp_chance = 0.01 AND memtable_flush_period_in_ms = 3600000 AND min_index_interval = 128 AND read_repair_chance = 0.0 + AND speculative_retry = '99.0PERCENTILE'; + +CREATE KEYSPACE simplex WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'} AND durable_writes = true; + +CREATE TABLE simplex."t1" ( + f1 int, + f2 int, + f3 int, + PRIMARY KEY (f1, f2) +) +WITH bloom_filter_fp_chance = 0.01 + AND caching = '{"keys":"ALL", "rows_per_partition":"NONE"}' + AND comment = 'test table' + AND compaction = {'class': 'SizeTieredCompactionStrategy'} + AND compression = {'sstable_compression': 'LZ4Compressor'} + AND dclocal_read_repair_chance = 0.1 + AND default_time_to_live = 1 + AND gc_grace_seconds = 2 + AND max_index_interval = 2048 + AND memtable_flush_period_in_ms = 3600000 + AND min_index_interval = 128 + AND read_repair_chance = 0.1 + AND speculative_retry = '99.0PERCENTILE'; + +CREATE INDEX "ind1" ON simplex."t1" ("f2"); + +CREATE TABLE simplex."t2" ( + f1 int, + f2 int, + f3 int, + PRIMARY KEY (f1, f2) +) +WITH CLUSTERING ORDER BY (f2 DESC) + AND COMPACT STORAGE + AND bloom_filter_fp_chance = 0.01 + AND caching = '{"keys":"ALL", "rows_per_partition":"NONE"}' + AND comment = 'compact test table' + AND compaction = {'class': 'SizeTieredCompactionStrategy'} + AND compression = {'sstable_compression': 'LZ4Compressor'} + AND dclocal_read_repair_chance = 0.1 + AND default_time_to_live = 1 + AND gc_grace_seconds = 2 + AND max_index_interval = 2048 + AND memtable_flush_period_in_ms = 3600000 + AND min_index_interval = 128 + AND read_repair_chance = 0.1 + AND speculative_retry = '99.0PERCENTILE'; + +CREATE TABLE simplex."t3" ( + f1 int PRIMARY KEY, + f2 int +) +WITH bloom_filter_fp_chance = 0.01 + AND caching = '{"keys":"ALL", "rows_per_partition":"NONE"}' + AND comment = 'test table one column key' + AND compaction = {'class': 'SizeTieredCompactionStrategy'} + AND compression = {'sstable_compression': 'LZ4Compressor'} + AND dclocal_read_repair_chance = 0.1 + AND default_time_to_live = 1 + AND gc_grace_seconds = 2 + AND max_index_interval = 2048 + AND memtable_flush_period_in_ms = 3600000 + AND min_index_interval = 128 + AND read_repair_chance = 0.1 AND speculative_retry = '99.0PERCENTILE'; \ No newline at end of file diff --git a/spec/cassandra/cluster/schema/fetchers/3.0.0-beta2-data.json b/spec/cassandra/cluster/schema/fetchers/3.0.0-beta2-data.json deleted file mode 100644 index 9d666f8e5..000000000 --- a/spec/cassandra/cluster/schema/fetchers/3.0.0-beta2-data.json +++ /dev/null @@ -1,2950 +0,0 @@ -{ - "SELECT * FROM system_schema.keyspaces": [ - { - "keyspace_name": "system_auth", - "durable_writes": true, - "replication": { - "class": "org.apache.cassandra.locator.SimpleStrategy", - "replication_factor": "1" - } - }, - { - "keyspace_name": "system_schema", - "durable_writes": true, - "replication": { - "class": "org.apache.cassandra.locator.LocalStrategy" - } - }, - { - "keyspace_name": "system_distributed", - "durable_writes": true, - "replication": { - "class": "org.apache.cassandra.locator.SimpleStrategy", - "replication_factor": "3" - } - }, - { - "keyspace_name": "system", - "durable_writes": true, - "replication": { - "class": "org.apache.cassandra.locator.LocalStrategy" - } - }, - { - "keyspace_name": "system_traces", - "durable_writes": true, - "replication": { - "class": "org.apache.cassandra.locator.SimpleStrategy", - "replication_factor": "2" - } - } - ], - "SELECT * FROM system_schema.tables": [ - { - "keyspace_name": "system_auth", - "table_name": "resource_role_permissons_index", - "bloom_filter_fp_chance": 0.01, - "caching": { - "keys": "ALL", - "rows_per_partition": "NONE" - }, - "comment": "index of db roles with permissions granted on a resource", - "compaction": { - "class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", - "max_threshold": "32", - "min_threshold": "4" - }, - "compression": { - "chunk_length_in_kb": "64", - "class": "org.apache.cassandra.io.compress.LZ4Compressor" - }, - "dclocal_read_repair_chance": 0.0, - "default_time_to_live": 0, - "extensions": { - }, - "flags": [ - "compound" - ], - "gc_grace_seconds": 7776000, - "id": "5f2fbdad-91f1-3946-bd25-d5da3a5c35ec", - "max_index_interval": 2048, - "memtable_flush_period_in_ms": 3600000, - "min_index_interval": 128, - "read_repair_chance": 0.0, - "speculative_retry": "99PERCENTILE" - }, - { - "keyspace_name": "system_auth", - "table_name": "role_members", - "bloom_filter_fp_chance": 0.01, - "caching": { - "keys": "ALL", - "rows_per_partition": "NONE" - }, - "comment": "role memberships lookup table", - "compaction": { - "class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", - "max_threshold": "32", - "min_threshold": "4" - }, - "compression": { - "chunk_length_in_kb": "64", - "class": "org.apache.cassandra.io.compress.LZ4Compressor" - }, - "dclocal_read_repair_chance": 0.0, - "default_time_to_live": 0, - "extensions": { - }, - "flags": [ - "compound" - ], - "gc_grace_seconds": 7776000, - "id": "0ecdaa87-f8fb-3e60-88d1-74fb36fe5c0d", - "max_index_interval": 2048, - "memtable_flush_period_in_ms": 3600000, - "min_index_interval": 128, - "read_repair_chance": 0.0, - "speculative_retry": "99PERCENTILE" - }, - { - "keyspace_name": "system_auth", - "table_name": "role_permissions", - "bloom_filter_fp_chance": 0.01, - "caching": { - "keys": "ALL", - "rows_per_partition": "NONE" - }, - "comment": "permissions granted to db roles", - "compaction": { - "class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", - "max_threshold": "32", - "min_threshold": "4" - }, - "compression": { - "chunk_length_in_kb": "64", - "class": "org.apache.cassandra.io.compress.LZ4Compressor" - }, - "dclocal_read_repair_chance": 0.0, - "default_time_to_live": 0, - "extensions": { - }, - "flags": [ - "compound" - ], - "gc_grace_seconds": 7776000, - "id": "3afbe79f-2194-31a7-add7-f5ab90d8ec9c", - "max_index_interval": 2048, - "memtable_flush_period_in_ms": 3600000, - "min_index_interval": 128, - "read_repair_chance": 0.0, - "speculative_retry": "99PERCENTILE" - }, - { - "keyspace_name": "system_auth", - "table_name": "roles", - "bloom_filter_fp_chance": 0.01, - "caching": { - "keys": "ALL", - "rows_per_partition": "NONE" - }, - "comment": "role definitions", - "compaction": { - "class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", - "max_threshold": "32", - "min_threshold": "4" - }, - "compression": { - "chunk_length_in_kb": "64", - "class": "org.apache.cassandra.io.compress.LZ4Compressor" - }, - "dclocal_read_repair_chance": 0.0, - "default_time_to_live": 0, - "extensions": { - }, - "flags": [ - "compound" - ], - "gc_grace_seconds": 7776000, - "id": "5bc52802-de25-35ed-aeab-188eecebb090", - "max_index_interval": 2048, - "memtable_flush_period_in_ms": 3600000, - "min_index_interval": 128, - "read_repair_chance": 0.0, - "speculative_retry": "99PERCENTILE" - }, - { - "keyspace_name": "system_schema", - "table_name": "aggregates", - "bloom_filter_fp_chance": 0.01, - "caching": { - "keys": "ALL", - "rows_per_partition": "NONE" - }, - "comment": "user defined aggregate definitions", - "compaction": { - "class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", - "max_threshold": "32", - "min_threshold": "4" - }, - "compression": { - "chunk_length_in_kb": "64", - "class": "org.apache.cassandra.io.compress.LZ4Compressor" - }, - "dclocal_read_repair_chance": 0.0, - "default_time_to_live": 0, - "extensions": { - }, - "flags": [ - "compound" - ], - "gc_grace_seconds": 604800, - "id": "924c5587-2e3a-345b-b10c-12f37c1ba895", - "max_index_interval": 2048, - "memtable_flush_period_in_ms": 3600000, - "min_index_interval": 128, - "read_repair_chance": 0.0, - "speculative_retry": "99PERCENTILE" - }, - { - "keyspace_name": "system_schema", - "table_name": "columns", - "bloom_filter_fp_chance": 0.01, - "caching": { - "keys": "ALL", - "rows_per_partition": "NONE" - }, - "comment": "column definitions", - "compaction": { - "class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", - "max_threshold": "32", - "min_threshold": "4" - }, - "compression": { - "chunk_length_in_kb": "64", - "class": "org.apache.cassandra.io.compress.LZ4Compressor" - }, - "dclocal_read_repair_chance": 0.0, - "default_time_to_live": 0, - "extensions": { - }, - "flags": [ - "compound" - ], - "gc_grace_seconds": 604800, - "id": "24101c25-a2ae-3af7-87c1-b40ee1aca33f", - "max_index_interval": 2048, - "memtable_flush_period_in_ms": 3600000, - "min_index_interval": 128, - "read_repair_chance": 0.0, - "speculative_retry": "99PERCENTILE" - }, - { - "keyspace_name": "system_schema", - "table_name": "dropped_columns", - "bloom_filter_fp_chance": 0.01, - "caching": { - "keys": "ALL", - "rows_per_partition": "NONE" - }, - "comment": "dropped column registry", - "compaction": { - "class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", - "max_threshold": "32", - "min_threshold": "4" - }, - "compression": { - "chunk_length_in_kb": "64", - "class": "org.apache.cassandra.io.compress.LZ4Compressor" - }, - "dclocal_read_repair_chance": 0.0, - "default_time_to_live": 0, - "extensions": { - }, - "flags": [ - "compound" - ], - "gc_grace_seconds": 604800, - "id": "5e7583b5-f3f4-3af1-9a39-b7e1d6f5f11f", - "max_index_interval": 2048, - "memtable_flush_period_in_ms": 3600000, - "min_index_interval": 128, - "read_repair_chance": 0.0, - "speculative_retry": "99PERCENTILE" - }, - { - "keyspace_name": "system_schema", - "table_name": "functions", - "bloom_filter_fp_chance": 0.01, - "caching": { - "keys": "ALL", - "rows_per_partition": "NONE" - }, - "comment": "user defined function definitions", - "compaction": { - "class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", - "max_threshold": "32", - "min_threshold": "4" - }, - "compression": { - "chunk_length_in_kb": "64", - "class": "org.apache.cassandra.io.compress.LZ4Compressor" - }, - "dclocal_read_repair_chance": 0.0, - "default_time_to_live": 0, - "extensions": { - }, - "flags": [ - "compound" - ], - "gc_grace_seconds": 604800, - "id": "96489b79-80be-3e14-a701-66a0b9159450", - "max_index_interval": 2048, - "memtable_flush_period_in_ms": 3600000, - "min_index_interval": 128, - "read_repair_chance": 0.0, - "speculative_retry": "99PERCENTILE" - }, - { - "keyspace_name": "system_schema", - "table_name": "indexes", - "bloom_filter_fp_chance": 0.01, - "caching": { - "keys": "ALL", - "rows_per_partition": "NONE" - }, - "comment": "secondary index definitions", - "compaction": { - "class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", - "max_threshold": "32", - "min_threshold": "4" - }, - "compression": { - "chunk_length_in_kb": "64", - "class": "org.apache.cassandra.io.compress.LZ4Compressor" - }, - "dclocal_read_repair_chance": 0.0, - "default_time_to_live": 0, - "extensions": { - }, - "flags": [ - "compound" - ], - "gc_grace_seconds": 604800, - "id": "0feb57ac-311f-382f-ba6d-9024d305702f", - "max_index_interval": 2048, - "memtable_flush_period_in_ms": 3600000, - "min_index_interval": 128, - "read_repair_chance": 0.0, - "speculative_retry": "99PERCENTILE" - }, - { - "keyspace_name": "system_schema", - "table_name": "keyspaces", - "bloom_filter_fp_chance": 0.01, - "caching": { - "keys": "ALL", - "rows_per_partition": "NONE" - }, - "comment": "keyspace definitions", - "compaction": { - "class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", - "max_threshold": "32", - "min_threshold": "4" - }, - "compression": { - "chunk_length_in_kb": "64", - "class": "org.apache.cassandra.io.compress.LZ4Compressor" - }, - "dclocal_read_repair_chance": 0.0, - "default_time_to_live": 0, - "extensions": { - }, - "flags": [ - "compound" - ], - "gc_grace_seconds": 604800, - "id": "abac5682-dea6-31c5-b535-b3d6cffd0fb6", - "max_index_interval": 2048, - "memtable_flush_period_in_ms": 3600000, - "min_index_interval": 128, - "read_repair_chance": 0.0, - "speculative_retry": "99PERCENTILE" - }, - { - "keyspace_name": "system_schema", - "table_name": "materialized_views", - "bloom_filter_fp_chance": 0.01, - "caching": { - "keys": "ALL", - "rows_per_partition": "NONE" - }, - "comment": "materialized views definitions", - "compaction": { - "class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", - "max_threshold": "32", - "min_threshold": "4" - }, - "compression": { - "chunk_length_in_kb": "64", - "class": "org.apache.cassandra.io.compress.LZ4Compressor" - }, - "dclocal_read_repair_chance": 0.0, - "default_time_to_live": 0, - "extensions": { - }, - "flags": [ - "compound" - ], - "gc_grace_seconds": 604800, - "id": "20b0f171-36aa-399a-b920-583ca6fa31bf", - "max_index_interval": 2048, - "memtable_flush_period_in_ms": 3600000, - "min_index_interval": 128, - "read_repair_chance": 0.0, - "speculative_retry": "99PERCENTILE" - }, - { - "keyspace_name": "system_schema", - "table_name": "tables", - "bloom_filter_fp_chance": 0.01, - "caching": { - "keys": "ALL", - "rows_per_partition": "NONE" - }, - "comment": "table definitions", - "compaction": { - "class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", - "max_threshold": "32", - "min_threshold": "4" - }, - "compression": { - "chunk_length_in_kb": "64", - "class": "org.apache.cassandra.io.compress.LZ4Compressor" - }, - "dclocal_read_repair_chance": 0.0, - "default_time_to_live": 0, - "extensions": { - }, - "flags": [ - "compound" - ], - "gc_grace_seconds": 604800, - "id": "afddfb9d-bc1e-3068-8056-eed6c302ba09", - "max_index_interval": 2048, - "memtable_flush_period_in_ms": 3600000, - "min_index_interval": 128, - "read_repair_chance": 0.0, - "speculative_retry": "99PERCENTILE" - }, - { - "keyspace_name": "system_schema", - "table_name": "triggers", - "bloom_filter_fp_chance": 0.01, - "caching": { - "keys": "ALL", - "rows_per_partition": "NONE" - }, - "comment": "trigger definitions", - "compaction": { - "class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", - "max_threshold": "32", - "min_threshold": "4" - }, - "compression": { - "chunk_length_in_kb": "64", - "class": "org.apache.cassandra.io.compress.LZ4Compressor" - }, - "dclocal_read_repair_chance": 0.0, - "default_time_to_live": 0, - "extensions": { - }, - "flags": [ - "compound" - ], - "gc_grace_seconds": 604800, - "id": "4df70b66-6b05-3251-95a1-32b54005fd48", - "max_index_interval": 2048, - "memtable_flush_period_in_ms": 3600000, - "min_index_interval": 128, - "read_repair_chance": 0.0, - "speculative_retry": "99PERCENTILE" - }, - { - "keyspace_name": "system_schema", - "table_name": "types", - "bloom_filter_fp_chance": 0.01, - "caching": { - "keys": "ALL", - "rows_per_partition": "NONE" - }, - "comment": "user defined type definitions", - "compaction": { - "class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", - "max_threshold": "32", - "min_threshold": "4" - }, - "compression": { - "chunk_length_in_kb": "64", - "class": "org.apache.cassandra.io.compress.LZ4Compressor" - }, - "dclocal_read_repair_chance": 0.0, - "default_time_to_live": 0, - "extensions": { - }, - "flags": [ - "compound" - ], - "gc_grace_seconds": 604800, - "id": "5a8b1ca8-6602-3f77-a045-9273d308917a", - "max_index_interval": 2048, - "memtable_flush_period_in_ms": 3600000, - "min_index_interval": 128, - "read_repair_chance": 0.0, - "speculative_retry": "99PERCENTILE" - }, - { - "keyspace_name": "system_distributed", - "table_name": "parent_repair_history", - "bloom_filter_fp_chance": 0.01, - "caching": { - "keys": "ALL", - "rows_per_partition": "NONE" - }, - "comment": "Repair history", - "compaction": { - "class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", - "max_threshold": "32", - "min_threshold": "4" - }, - "compression": { - "chunk_length_in_kb": "64", - "class": "org.apache.cassandra.io.compress.LZ4Compressor" - }, - "dclocal_read_repair_chance": 0.0, - "default_time_to_live": 0, - "extensions": { - }, - "flags": [ - "compound" - ], - "gc_grace_seconds": 0, - "id": "deabd734-b99d-3b9c-92e5-fd92eb5abf14", - "max_index_interval": 2048, - "memtable_flush_period_in_ms": 3600000, - "min_index_interval": 128, - "read_repair_chance": 0.0, - "speculative_retry": "99PERCENTILE" - }, - { - "keyspace_name": "system_distributed", - "table_name": "repair_history", - "bloom_filter_fp_chance": 0.01, - "caching": { - "keys": "ALL", - "rows_per_partition": "NONE" - }, - "comment": "Repair history", - "compaction": { - "class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", - "max_threshold": "32", - "min_threshold": "4" - }, - "compression": { - "chunk_length_in_kb": "64", - "class": "org.apache.cassandra.io.compress.LZ4Compressor" - }, - "dclocal_read_repair_chance": 0.0, - "default_time_to_live": 0, - "extensions": { - }, - "flags": [ - "compound" - ], - "gc_grace_seconds": 0, - "id": "759fffad-624b-3181-80ee-fa9a52d1f627", - "max_index_interval": 2048, - "memtable_flush_period_in_ms": 3600000, - "min_index_interval": 128, - "read_repair_chance": 0.0, - "speculative_retry": "99PERCENTILE" - }, - { - "keyspace_name": "system", - "table_name": "IndexInfo", - "bloom_filter_fp_chance": 0.01, - "caching": { - "keys": "ALL", - "rows_per_partition": "NONE" - }, - "comment": "built column indexes", - "compaction": { - "class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", - "max_threshold": "32", - "min_threshold": "4" - }, - "compression": { - "chunk_length_in_kb": "64", - "class": "org.apache.cassandra.io.compress.LZ4Compressor" - }, - "dclocal_read_repair_chance": 0.0, - "default_time_to_live": 0, - "extensions": { - }, - "flags": [ - "dense" - ], - "gc_grace_seconds": 0, - "id": "9f5c6374-d485-3229-9a0a-5094af9ad1e3", - "max_index_interval": 2048, - "memtable_flush_period_in_ms": 3600000, - "min_index_interval": 128, - "read_repair_chance": 0.0, - "speculative_retry": "99PERCENTILE" - }, - { - "keyspace_name": "system", - "table_name": "available_ranges", - "bloom_filter_fp_chance": 0.01, - "caching": { - "keys": "ALL", - "rows_per_partition": "NONE" - }, - "comment": "available keyspace/ranges during bootstrap/replace that are ready to be served", - "compaction": { - "class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", - "max_threshold": "32", - "min_threshold": "4" - }, - "compression": { - "chunk_length_in_kb": "64", - "class": "org.apache.cassandra.io.compress.LZ4Compressor" - }, - "dclocal_read_repair_chance": 0.0, - "default_time_to_live": 0, - "extensions": { - }, - "flags": [ - "compound" - ], - "gc_grace_seconds": 0, - "id": "c539fcab-d65a-31d1-8133-d25605643ee3", - "max_index_interval": 2048, - "memtable_flush_period_in_ms": 3600000, - "min_index_interval": 128, - "read_repair_chance": 0.0, - "speculative_retry": "99PERCENTILE" - }, - { - "keyspace_name": "system", - "table_name": "batches", - "bloom_filter_fp_chance": 0.01, - "caching": { - "keys": "ALL", - "rows_per_partition": "NONE" - }, - "comment": "batches awaiting replay", - "compaction": { - "class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", - "max_threshold": "32", - "min_threshold": "2" - }, - "compression": { - "chunk_length_in_kb": "64", - "class": "org.apache.cassandra.io.compress.LZ4Compressor" - }, - "dclocal_read_repair_chance": 0.0, - "default_time_to_live": 0, - "extensions": { - }, - "flags": [ - "compound" - ], - "gc_grace_seconds": 0, - "id": "919a4bc5-7a33-3573-b03e-13fc3f68b465", - "max_index_interval": 2048, - "memtable_flush_period_in_ms": 3600000, - "min_index_interval": 128, - "read_repair_chance": 0.0, - "speculative_retry": "99PERCENTILE" - }, - { - "keyspace_name": "system", - "table_name": "batchlog", - "bloom_filter_fp_chance": 0.01, - "caching": { - "keys": "ALL", - "rows_per_partition": "NONE" - }, - "comment": "*DEPRECATED* batchlog entries", - "compaction": { - "class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", - "max_threshold": "32", - "min_threshold": "2" - }, - "compression": { - "chunk_length_in_kb": "64", - "class": "org.apache.cassandra.io.compress.LZ4Compressor" - }, - "dclocal_read_repair_chance": 0.0, - "default_time_to_live": 0, - "extensions": { - }, - "flags": [ - "compound" - ], - "gc_grace_seconds": 0, - "id": "0290003c-977e-397c-ac3e-fdfdc01d626b", - "max_index_interval": 2048, - "memtable_flush_period_in_ms": 3600000, - "min_index_interval": 128, - "read_repair_chance": 0.0, - "speculative_retry": "99PERCENTILE" - }, - { - "keyspace_name": "system", - "table_name": "built_materialized_views", - "bloom_filter_fp_chance": 0.01, - "caching": { - "keys": "ALL", - "rows_per_partition": "NONE" - }, - "comment": "built materialized views", - "compaction": { - "class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", - "max_threshold": "32", - "min_threshold": "4" - }, - "compression": { - "chunk_length_in_kb": "64", - "class": "org.apache.cassandra.io.compress.LZ4Compressor" - }, - "dclocal_read_repair_chance": 0.0, - "default_time_to_live": 0, - "extensions": { - }, - "flags": [ - "compound" - ], - "gc_grace_seconds": 0, - "id": "08eb3b92-7c33-36b4-9e17-292203594304", - "max_index_interval": 2048, - "memtable_flush_period_in_ms": 3600000, - "min_index_interval": 128, - "read_repair_chance": 0.0, - "speculative_retry": "99PERCENTILE" - }, - { - "keyspace_name": "system", - "table_name": "compaction_history", - "bloom_filter_fp_chance": 0.01, - "caching": { - "keys": "ALL", - "rows_per_partition": "NONE" - }, - "comment": "week-long compaction history", - "compaction": { - "class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", - "max_threshold": "32", - "min_threshold": "4" - }, - "compression": { - "chunk_length_in_kb": "64", - "class": "org.apache.cassandra.io.compress.LZ4Compressor" - }, - "dclocal_read_repair_chance": 0.0, - "default_time_to_live": 604800, - "extensions": { - }, - "flags": [ - "compound" - ], - "gc_grace_seconds": 0, - "id": "b4dbb7b4-dc49-3fb5-b3bf-ce6e434832ca", - "max_index_interval": 2048, - "memtable_flush_period_in_ms": 3600000, - "min_index_interval": 128, - "read_repair_chance": 0.0, - "speculative_retry": "99PERCENTILE" - }, - { - "keyspace_name": "system", - "table_name": "hints", - "bloom_filter_fp_chance": 0.01, - "caching": { - "keys": "ALL", - "rows_per_partition": "NONE" - }, - "comment": "*DEPRECATED* hints awaiting delivery", - "compaction": { - "class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", - "enabled": "false", - "max_threshold": "32", - "min_threshold": "4" - }, - "compression": { - "chunk_length_in_kb": "64", - "class": "org.apache.cassandra.io.compress.LZ4Compressor" - }, - "dclocal_read_repair_chance": 0.0, - "default_time_to_live": 0, - "extensions": { - }, - "flags": [ - "compound", - "dense" - ], - "gc_grace_seconds": 0, - "id": "2666e205-73ef-38b3-90fe-fecf96e8f0c7", - "max_index_interval": 2048, - "memtable_flush_period_in_ms": 3600000, - "min_index_interval": 128, - "read_repair_chance": 0.0, - "speculative_retry": "99PERCENTILE" - }, - { - "keyspace_name": "system", - "table_name": "local", - "bloom_filter_fp_chance": 0.01, - "caching": { - "keys": "ALL", - "rows_per_partition": "NONE" - }, - "comment": "information about the local node", - "compaction": { - "class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", - "max_threshold": "32", - "min_threshold": "4" - }, - "compression": { - "chunk_length_in_kb": "64", - "class": "org.apache.cassandra.io.compress.LZ4Compressor" - }, - "dclocal_read_repair_chance": 0.0, - "default_time_to_live": 0, - "extensions": { - }, - "flags": [ - "compound" - ], - "gc_grace_seconds": 0, - "id": "7ad54392-bcdd-35a6-8417-4e047860b377", - "max_index_interval": 2048, - "memtable_flush_period_in_ms": 3600000, - "min_index_interval": 128, - "read_repair_chance": 0.0, - "speculative_retry": "99PERCENTILE" - }, - { - "keyspace_name": "system", - "table_name": "materialized_views_builds_in_progress", - "bloom_filter_fp_chance": 0.01, - "caching": { - "keys": "ALL", - "rows_per_partition": "NONE" - }, - "comment": "materialized views builds current progress", - "compaction": { - "class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", - "max_threshold": "32", - "min_threshold": "4" - }, - "compression": { - "chunk_length_in_kb": "64", - "class": "org.apache.cassandra.io.compress.LZ4Compressor" - }, - "dclocal_read_repair_chance": 0.0, - "default_time_to_live": 0, - "extensions": { - }, - "flags": [ - "compound" - ], - "gc_grace_seconds": 0, - "id": "1303e99a-d343-3fdb-bdfe-086aa9c6797d", - "max_index_interval": 2048, - "memtable_flush_period_in_ms": 3600000, - "min_index_interval": 128, - "read_repair_chance": 0.0, - "speculative_retry": "99PERCENTILE" - }, - { - "keyspace_name": "system", - "table_name": "paxos", - "bloom_filter_fp_chance": 0.01, - "caching": { - "keys": "ALL", - "rows_per_partition": "NONE" - }, - "comment": "in-progress paxos proposals", - "compaction": { - "class": "org.apache.cassandra.db.compaction.LeveledCompactionStrategy" - }, - "compression": { - "chunk_length_in_kb": "64", - "class": "org.apache.cassandra.io.compress.LZ4Compressor" - }, - "dclocal_read_repair_chance": 0.0, - "default_time_to_live": 0, - "extensions": { - }, - "flags": [ - "compound" - ], - "gc_grace_seconds": 0, - "id": "b7b7f0c2-fd0a-3410-8c05-3ef614bb7c2d", - "max_index_interval": 2048, - "memtable_flush_period_in_ms": 3600000, - "min_index_interval": 128, - "read_repair_chance": 0.0, - "speculative_retry": "99PERCENTILE" - }, - { - "keyspace_name": "system", - "table_name": "peer_events", - "bloom_filter_fp_chance": 0.01, - "caching": { - "keys": "ALL", - "rows_per_partition": "NONE" - }, - "comment": "events related to peers", - "compaction": { - "class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", - "max_threshold": "32", - "min_threshold": "4" - }, - "compression": { - "chunk_length_in_kb": "64", - "class": "org.apache.cassandra.io.compress.LZ4Compressor" - }, - "dclocal_read_repair_chance": 0.0, - "default_time_to_live": 0, - "extensions": { - }, - "flags": [ - "compound" - ], - "gc_grace_seconds": 0, - "id": "59dfeaea-8db2-3341-91ef-109974d81484", - "max_index_interval": 2048, - "memtable_flush_period_in_ms": 3600000, - "min_index_interval": 128, - "read_repair_chance": 0.0, - "speculative_retry": "99PERCENTILE" - }, - { - "keyspace_name": "system", - "table_name": "peers", - "bloom_filter_fp_chance": 0.01, - "caching": { - "keys": "ALL", - "rows_per_partition": "NONE" - }, - "comment": "information about known peers in the cluster", - "compaction": { - "class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", - "max_threshold": "32", - "min_threshold": "4" - }, - "compression": { - "chunk_length_in_kb": "64", - "class": "org.apache.cassandra.io.compress.LZ4Compressor" - }, - "dclocal_read_repair_chance": 0.0, - "default_time_to_live": 0, - "extensions": { - }, - "flags": [ - "compound" - ], - "gc_grace_seconds": 0, - "id": "37f71aca-7dc2-383b-a706-72528af04d4f", - "max_index_interval": 2048, - "memtable_flush_period_in_ms": 3600000, - "min_index_interval": 128, - "read_repair_chance": 0.0, - "speculative_retry": "99PERCENTILE" - }, - { - "keyspace_name": "system", - "table_name": "range_xfers", - "bloom_filter_fp_chance": 0.01, - "caching": { - "keys": "ALL", - "rows_per_partition": "NONE" - }, - "comment": "ranges requested for transfer", - "compaction": { - "class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", - "max_threshold": "32", - "min_threshold": "4" - }, - "compression": { - "chunk_length_in_kb": "64", - "class": "org.apache.cassandra.io.compress.LZ4Compressor" - }, - "dclocal_read_repair_chance": 0.0, - "default_time_to_live": 0, - "extensions": { - }, - "flags": [ - "compound" - ], - "gc_grace_seconds": 0, - "id": "55d76438-4e55-3f8b-9f6e-676d4af3976d", - "max_index_interval": 2048, - "memtable_flush_period_in_ms": 3600000, - "min_index_interval": 128, - "read_repair_chance": 0.0, - "speculative_retry": "99PERCENTILE" - }, - { - "keyspace_name": "system", - "table_name": "size_estimates", - "bloom_filter_fp_chance": 0.01, - "caching": { - "keys": "ALL", - "rows_per_partition": "NONE" - }, - "comment": "per-table primary range size estimates", - "compaction": { - "class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", - "max_threshold": "32", - "min_threshold": "4" - }, - "compression": { - "chunk_length_in_kb": "64", - "class": "org.apache.cassandra.io.compress.LZ4Compressor" - }, - "dclocal_read_repair_chance": 0.0, - "default_time_to_live": 0, - "extensions": { - }, - "flags": [ - "compound" - ], - "gc_grace_seconds": 0, - "id": "618f817b-005f-3678-b8a4-53f3930b8e86", - "max_index_interval": 2048, - "memtable_flush_period_in_ms": 3600000, - "min_index_interval": 128, - "read_repair_chance": 0.0, - "speculative_retry": "99PERCENTILE" - }, - { - "keyspace_name": "system", - "table_name": "sstable_activity", - "bloom_filter_fp_chance": 0.01, - "caching": { - "keys": "ALL", - "rows_per_partition": "NONE" - }, - "comment": "historic sstable read rates", - "compaction": { - "class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", - "max_threshold": "32", - "min_threshold": "4" - }, - "compression": { - "chunk_length_in_kb": "64", - "class": "org.apache.cassandra.io.compress.LZ4Compressor" - }, - "dclocal_read_repair_chance": 0.0, - "default_time_to_live": 0, - "extensions": { - }, - "flags": [ - "compound" - ], - "gc_grace_seconds": 0, - "id": "5a1ff267-ace0-3f12-8563-cfae6103c65e", - "max_index_interval": 2048, - "memtable_flush_period_in_ms": 3600000, - "min_index_interval": 128, - "read_repair_chance": 0.0, - "speculative_retry": "99PERCENTILE" - }, - { - "keyspace_name": "system_traces", - "table_name": "events", - "bloom_filter_fp_chance": 0.01, - "caching": { - "keys": "ALL", - "rows_per_partition": "NONE" - }, - "comment": "tracing events", - "compaction": { - "class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", - "max_threshold": "32", - "min_threshold": "4" - }, - "compression": { - "chunk_length_in_kb": "64", - "class": "org.apache.cassandra.io.compress.LZ4Compressor" - }, - "dclocal_read_repair_chance": 0.0, - "default_time_to_live": 0, - "extensions": { - }, - "flags": [ - "compound" - ], - "gc_grace_seconds": 0, - "id": "8826e8e9-e16a-3728-8753-3bc1fc713c25", - "max_index_interval": 2048, - "memtable_flush_period_in_ms": 3600000, - "min_index_interval": 128, - "read_repair_chance": 0.0, - "speculative_retry": "99PERCENTILE" - }, - { - "keyspace_name": "system_traces", - "table_name": "sessions", - "bloom_filter_fp_chance": 0.01, - "caching": { - "keys": "ALL", - "rows_per_partition": "NONE" - }, - "comment": "tracing sessions", - "compaction": { - "class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", - "max_threshold": "32", - "min_threshold": "4" - }, - "compression": { - "chunk_length_in_kb": "64", - "class": "org.apache.cassandra.io.compress.LZ4Compressor" - }, - "dclocal_read_repair_chance": 0.0, - "default_time_to_live": 0, - "extensions": { - }, - "flags": [ - "compound" - ], - "gc_grace_seconds": 0, - "id": "c5e99f16-8677-3914-b17e-960613512345", - "max_index_interval": 2048, - "memtable_flush_period_in_ms": 3600000, - "min_index_interval": 128, - "read_repair_chance": 0.0, - "speculative_retry": "99PERCENTILE" - } - ], - "SELECT * FROM system_schema.columns": [ - { - "keyspace_name": "system_auth", - "table_name": "resource_role_permissons_index", - "column_name": "resource", - "column_name_bytes": "resource", - "component_index": null, - "type": "partition_key", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_auth", - "table_name": "resource_role_permissons_index", - "column_name": "role", - "column_name_bytes": "role", - "component_index": 0, - "type": "clustering", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_auth", - "table_name": "role_members", - "column_name": "member", - "column_name_bytes": "member", - "component_index": 0, - "type": "clustering", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_auth", - "table_name": "role_members", - "column_name": "role", - "column_name_bytes": "role", - "component_index": null, - "type": "partition_key", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_auth", - "table_name": "role_permissions", - "column_name": "permissions", - "column_name_bytes": "permissions", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.SetType(org.apache.cassandra.db.marshal.UTF8Type)" - }, - { - "keyspace_name": "system_auth", - "table_name": "role_permissions", - "column_name": "resource", - "column_name_bytes": "resource", - "component_index": 0, - "type": "clustering", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_auth", - "table_name": "role_permissions", - "column_name": "role", - "column_name_bytes": "role", - "component_index": null, - "type": "partition_key", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_auth", - "table_name": "roles", - "column_name": "can_login", - "column_name_bytes": "can_login", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.BooleanType" - }, - { - "keyspace_name": "system_auth", - "table_name": "roles", - "column_name": "is_superuser", - "column_name_bytes": "is_superuser", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.BooleanType" - }, - { - "keyspace_name": "system_auth", - "table_name": "roles", - "column_name": "member_of", - "column_name_bytes": "member_of", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.SetType(org.apache.cassandra.db.marshal.UTF8Type)" - }, - { - "keyspace_name": "system_auth", - "table_name": "roles", - "column_name": "role", - "column_name_bytes": "role", - "component_index": null, - "type": "partition_key", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_auth", - "table_name": "roles", - "column_name": "salted_hash", - "column_name_bytes": "salted_hash", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "aggregates", - "column_name": "aggregate_name", - "column_name_bytes": "aggregate_name", - "component_index": 0, - "type": "clustering", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "aggregates", - "column_name": "argument_types", - "column_name_bytes": "argument_types", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.FrozenType(org.apache.cassandra.db.marshal.ListType(org.apache.cassandra.db.marshal.UTF8Type))" - }, - { - "keyspace_name": "system_schema", - "table_name": "aggregates", - "column_name": "final_func", - "column_name_bytes": "final_func", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "aggregates", - "column_name": "initcond", - "column_name_bytes": "initcond", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.BytesType" - }, - { - "keyspace_name": "system_schema", - "table_name": "aggregates", - "column_name": "keyspace_name", - "column_name_bytes": "keyspace_name", - "component_index": null, - "type": "partition_key", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "aggregates", - "column_name": "return_type", - "column_name_bytes": "return_type", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "aggregates", - "column_name": "signature", - "column_name_bytes": "signature", - "component_index": 1, - "type": "clustering", - "validator": "org.apache.cassandra.db.marshal.FrozenType(org.apache.cassandra.db.marshal.ListType(org.apache.cassandra.db.marshal.UTF8Type))" - }, - { - "keyspace_name": "system_schema", - "table_name": "aggregates", - "column_name": "state_func", - "column_name_bytes": "state_func", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "aggregates", - "column_name": "state_type", - "column_name_bytes": "state_type", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "columns", - "column_name": "column_name", - "column_name_bytes": "column_name", - "component_index": 1, - "type": "clustering", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "columns", - "column_name": "column_name_bytes", - "column_name_bytes": "column_name_bytes", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.BytesType" - }, - { - "keyspace_name": "system_schema", - "table_name": "columns", - "column_name": "component_index", - "column_name_bytes": "component_index", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.Int32Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "columns", - "column_name": "keyspace_name", - "column_name_bytes": "keyspace_name", - "component_index": null, - "type": "partition_key", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "columns", - "column_name": "table_name", - "column_name_bytes": "table_name", - "component_index": 0, - "type": "clustering", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "columns", - "column_name": "type", - "column_name_bytes": "type", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "columns", - "column_name": "validator", - "column_name_bytes": "validator", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "dropped_columns", - "column_name": "column_name", - "column_name_bytes": "column_name", - "component_index": 1, - "type": "clustering", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "dropped_columns", - "column_name": "dropped_time", - "column_name_bytes": "dropped_time", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.TimestampType" - }, - { - "keyspace_name": "system_schema", - "table_name": "dropped_columns", - "column_name": "keyspace_name", - "column_name_bytes": "keyspace_name", - "component_index": null, - "type": "partition_key", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "dropped_columns", - "column_name": "table_name", - "column_name_bytes": "table_name", - "component_index": 0, - "type": "clustering", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "dropped_columns", - "column_name": "type", - "column_name_bytes": "type", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "functions", - "column_name": "argument_names", - "column_name_bytes": "argument_names", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.FrozenType(org.apache.cassandra.db.marshal.ListType(org.apache.cassandra.db.marshal.UTF8Type))" - }, - { - "keyspace_name": "system_schema", - "table_name": "functions", - "column_name": "argument_types", - "column_name_bytes": "argument_types", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.FrozenType(org.apache.cassandra.db.marshal.ListType(org.apache.cassandra.db.marshal.UTF8Type))" - }, - { - "keyspace_name": "system_schema", - "table_name": "functions", - "column_name": "body", - "column_name_bytes": "body", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "functions", - "column_name": "called_on_null_input", - "column_name_bytes": "called_on_null_input", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.BooleanType" - }, - { - "keyspace_name": "system_schema", - "table_name": "functions", - "column_name": "function_name", - "column_name_bytes": "function_name", - "component_index": 0, - "type": "clustering", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "functions", - "column_name": "keyspace_name", - "column_name_bytes": "keyspace_name", - "component_index": null, - "type": "partition_key", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "functions", - "column_name": "language", - "column_name_bytes": "language", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "functions", - "column_name": "return_type", - "column_name_bytes": "return_type", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "functions", - "column_name": "signature", - "column_name_bytes": "signature", - "component_index": 1, - "type": "clustering", - "validator": "org.apache.cassandra.db.marshal.FrozenType(org.apache.cassandra.db.marshal.ListType(org.apache.cassandra.db.marshal.UTF8Type))" - }, - { - "keyspace_name": "system_schema", - "table_name": "indexes", - "column_name": "index_name", - "column_name_bytes": "index_name", - "component_index": 1, - "type": "clustering", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "indexes", - "column_name": "index_type", - "column_name_bytes": "index_type", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "indexes", - "column_name": "keyspace_name", - "column_name_bytes": "keyspace_name", - "component_index": null, - "type": "partition_key", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "indexes", - "column_name": "options", - "column_name_bytes": "options", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.FrozenType(org.apache.cassandra.db.marshal.MapType(org.apache.cassandra.db.marshal.UTF8Type,org.apache.cassandra.db.marshal.UTF8Type))" - }, - { - "keyspace_name": "system_schema", - "table_name": "indexes", - "column_name": "table_name", - "column_name_bytes": "table_name", - "component_index": 0, - "type": "clustering", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "indexes", - "column_name": "target_columns", - "column_name_bytes": "target_columns", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.FrozenType(org.apache.cassandra.db.marshal.SetType(org.apache.cassandra.db.marshal.UTF8Type))" - }, - { - "keyspace_name": "system_schema", - "table_name": "indexes", - "column_name": "target_type", - "column_name_bytes": "target_type", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "keyspaces", - "column_name": "durable_writes", - "column_name_bytes": "durable_writes", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.BooleanType" - }, - { - "keyspace_name": "system_schema", - "table_name": "keyspaces", - "column_name": "keyspace_name", - "column_name_bytes": "keyspace_name", - "component_index": null, - "type": "partition_key", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "keyspaces", - "column_name": "replication", - "column_name_bytes": "replication", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.FrozenType(org.apache.cassandra.db.marshal.MapType(org.apache.cassandra.db.marshal.UTF8Type,org.apache.cassandra.db.marshal.UTF8Type))" - }, - { - "keyspace_name": "system_schema", - "table_name": "materialized_views", - "column_name": "clustering_columns", - "column_name_bytes": "clustering_columns", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.FrozenType(org.apache.cassandra.db.marshal.ListType(org.apache.cassandra.db.marshal.UTF8Type))" - }, - { - "keyspace_name": "system_schema", - "table_name": "materialized_views", - "column_name": "included_columns", - "column_name_bytes": "included_columns", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.FrozenType(org.apache.cassandra.db.marshal.ListType(org.apache.cassandra.db.marshal.UTF8Type))" - }, - { - "keyspace_name": "system_schema", - "table_name": "materialized_views", - "column_name": "keyspace_name", - "column_name_bytes": "keyspace_name", - "component_index": null, - "type": "partition_key", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "materialized_views", - "column_name": "table_name", - "column_name_bytes": "table_name", - "component_index": 0, - "type": "clustering", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "materialized_views", - "column_name": "target_columns", - "column_name_bytes": "target_columns", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.FrozenType(org.apache.cassandra.db.marshal.ListType(org.apache.cassandra.db.marshal.UTF8Type))" - }, - { - "keyspace_name": "system_schema", - "table_name": "materialized_views", - "column_name": "view_name", - "column_name_bytes": "view_name", - "component_index": 1, - "type": "clustering", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "tables", - "column_name": "bloom_filter_fp_chance", - "column_name_bytes": "bloom_filter_fp_chance", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.DoubleType" - }, - { - "keyspace_name": "system_schema", - "table_name": "tables", - "column_name": "caching", - "column_name_bytes": "caching", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.FrozenType(org.apache.cassandra.db.marshal.MapType(org.apache.cassandra.db.marshal.UTF8Type,org.apache.cassandra.db.marshal.UTF8Type))" - }, - { - "keyspace_name": "system_schema", - "table_name": "tables", - "column_name": "comment", - "column_name_bytes": "comment", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "tables", - "column_name": "compaction", - "column_name_bytes": "compaction", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.FrozenType(org.apache.cassandra.db.marshal.MapType(org.apache.cassandra.db.marshal.UTF8Type,org.apache.cassandra.db.marshal.UTF8Type))" - }, - { - "keyspace_name": "system_schema", - "table_name": "tables", - "column_name": "compression", - "column_name_bytes": "compression", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.FrozenType(org.apache.cassandra.db.marshal.MapType(org.apache.cassandra.db.marshal.UTF8Type,org.apache.cassandra.db.marshal.UTF8Type))" - }, - { - "keyspace_name": "system_schema", - "table_name": "tables", - "column_name": "dclocal_read_repair_chance", - "column_name_bytes": "dclocal_read_repair_chance", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.DoubleType" - }, - { - "keyspace_name": "system_schema", - "table_name": "tables", - "column_name": "default_time_to_live", - "column_name_bytes": "default_time_to_live", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.Int32Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "tables", - "column_name": "extensions", - "column_name_bytes": "extensions", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.FrozenType(org.apache.cassandra.db.marshal.MapType(org.apache.cassandra.db.marshal.UTF8Type,org.apache.cassandra.db.marshal.BytesType))" - }, - { - "keyspace_name": "system_schema", - "table_name": "tables", - "column_name": "flags", - "column_name_bytes": "flags", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.FrozenType(org.apache.cassandra.db.marshal.SetType(org.apache.cassandra.db.marshal.UTF8Type))" - }, - { - "keyspace_name": "system_schema", - "table_name": "tables", - "column_name": "gc_grace_seconds", - "column_name_bytes": "gc_grace_seconds", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.Int32Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "tables", - "column_name": "id", - "column_name_bytes": "id", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UUIDType" - }, - { - "keyspace_name": "system_schema", - "table_name": "tables", - "column_name": "keyspace_name", - "column_name_bytes": "keyspace_name", - "component_index": null, - "type": "partition_key", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "tables", - "column_name": "max_index_interval", - "column_name_bytes": "max_index_interval", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.Int32Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "tables", - "column_name": "memtable_flush_period_in_ms", - "column_name_bytes": "memtable_flush_period_in_ms", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.Int32Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "tables", - "column_name": "min_index_interval", - "column_name_bytes": "min_index_interval", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.Int32Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "tables", - "column_name": "read_repair_chance", - "column_name_bytes": "read_repair_chance", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.DoubleType" - }, - { - "keyspace_name": "system_schema", - "table_name": "tables", - "column_name": "speculative_retry", - "column_name_bytes": "speculative_retry", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "tables", - "column_name": "table_name", - "column_name_bytes": "table_name", - "component_index": 0, - "type": "clustering", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "triggers", - "column_name": "keyspace_name", - "column_name_bytes": "keyspace_name", - "component_index": null, - "type": "partition_key", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "triggers", - "column_name": "table_name", - "column_name_bytes": "table_name", - "component_index": 0, - "type": "clustering", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "triggers", - "column_name": "trigger_name", - "column_name_bytes": "trigger_name", - "component_index": 1, - "type": "clustering", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "triggers", - "column_name": "trigger_options", - "column_name_bytes": "trigger_options", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.FrozenType(org.apache.cassandra.db.marshal.MapType(org.apache.cassandra.db.marshal.UTF8Type,org.apache.cassandra.db.marshal.UTF8Type))" - }, - { - "keyspace_name": "system_schema", - "table_name": "types", - "column_name": "field_names", - "column_name_bytes": "field_names", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.FrozenType(org.apache.cassandra.db.marshal.ListType(org.apache.cassandra.db.marshal.UTF8Type))" - }, - { - "keyspace_name": "system_schema", - "table_name": "types", - "column_name": "field_types", - "column_name_bytes": "field_types", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.FrozenType(org.apache.cassandra.db.marshal.ListType(org.apache.cassandra.db.marshal.UTF8Type))" - }, - { - "keyspace_name": "system_schema", - "table_name": "types", - "column_name": "keyspace_name", - "column_name_bytes": "keyspace_name", - "component_index": null, - "type": "partition_key", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_schema", - "table_name": "types", - "column_name": "type_name", - "column_name_bytes": "type_name", - "component_index": 0, - "type": "clustering", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_distributed", - "table_name": "parent_repair_history", - "column_name": "columnfamily_names", - "column_name_bytes": "columnfamily_names", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.SetType(org.apache.cassandra.db.marshal.UTF8Type)" - }, - { - "keyspace_name": "system_distributed", - "table_name": "parent_repair_history", - "column_name": "exception_message", - "column_name_bytes": "exception_message", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_distributed", - "table_name": "parent_repair_history", - "column_name": "exception_stacktrace", - "column_name_bytes": "exception_stacktrace", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_distributed", - "table_name": "parent_repair_history", - "column_name": "finished_at", - "column_name_bytes": "finished_at", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.TimestampType" - }, - { - "keyspace_name": "system_distributed", - "table_name": "parent_repair_history", - "column_name": "keyspace_name", - "column_name_bytes": "keyspace_name", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_distributed", - "table_name": "parent_repair_history", - "column_name": "parent_id", - "column_name_bytes": "parent_id", - "component_index": null, - "type": "partition_key", - "validator": "org.apache.cassandra.db.marshal.TimeUUIDType" - }, - { - "keyspace_name": "system_distributed", - "table_name": "parent_repair_history", - "column_name": "requested_ranges", - "column_name_bytes": "requested_ranges", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.SetType(org.apache.cassandra.db.marshal.UTF8Type)" - }, - { - "keyspace_name": "system_distributed", - "table_name": "parent_repair_history", - "column_name": "started_at", - "column_name_bytes": "started_at", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.TimestampType" - }, - { - "keyspace_name": "system_distributed", - "table_name": "parent_repair_history", - "column_name": "successful_ranges", - "column_name_bytes": "successful_ranges", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.SetType(org.apache.cassandra.db.marshal.UTF8Type)" - }, - { - "keyspace_name": "system_distributed", - "table_name": "repair_history", - "column_name": "columnfamily_name", - "column_name_bytes": "columnfamily_name", - "component_index": 1, - "type": "partition_key", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_distributed", - "table_name": "repair_history", - "column_name": "coordinator", - "column_name_bytes": "coordinator", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.InetAddressType" - }, - { - "keyspace_name": "system_distributed", - "table_name": "repair_history", - "column_name": "exception_message", - "column_name_bytes": "exception_message", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_distributed", - "table_name": "repair_history", - "column_name": "exception_stacktrace", - "column_name_bytes": "exception_stacktrace", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_distributed", - "table_name": "repair_history", - "column_name": "finished_at", - "column_name_bytes": "finished_at", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.TimestampType" - }, - { - "keyspace_name": "system_distributed", - "table_name": "repair_history", - "column_name": "id", - "column_name_bytes": "id", - "component_index": 0, - "type": "clustering", - "validator": "org.apache.cassandra.db.marshal.TimeUUIDType" - }, - { - "keyspace_name": "system_distributed", - "table_name": "repair_history", - "column_name": "keyspace_name", - "column_name_bytes": "keyspace_name", - "component_index": 0, - "type": "partition_key", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_distributed", - "table_name": "repair_history", - "column_name": "parent_id", - "column_name_bytes": "parent_id", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.TimeUUIDType" - }, - { - "keyspace_name": "system_distributed", - "table_name": "repair_history", - "column_name": "participants", - "column_name_bytes": "participants", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.SetType(org.apache.cassandra.db.marshal.InetAddressType)" - }, - { - "keyspace_name": "system_distributed", - "table_name": "repair_history", - "column_name": "range_begin", - "column_name_bytes": "range_begin", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_distributed", - "table_name": "repair_history", - "column_name": "range_end", - "column_name_bytes": "range_end", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_distributed", - "table_name": "repair_history", - "column_name": "started_at", - "column_name_bytes": "started_at", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.TimestampType" - }, - { - "keyspace_name": "system_distributed", - "table_name": "repair_history", - "column_name": "status", - "column_name_bytes": "status", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system", - "table_name": "IndexInfo", - "column_name": "index_name", - "column_name_bytes": "index_name", - "component_index": 0, - "type": "clustering", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system", - "table_name": "IndexInfo", - "column_name": "table_name", - "column_name_bytes": "table_name", - "component_index": null, - "type": "partition_key", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system", - "table_name": "IndexInfo", - "column_name": "value", - "column_name_bytes": "value", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.EmptyType" - }, - { - "keyspace_name": "system", - "table_name": "available_ranges", - "column_name": "keyspace_name", - "column_name_bytes": "keyspace_name", - "component_index": null, - "type": "partition_key", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system", - "table_name": "available_ranges", - "column_name": "ranges", - "column_name_bytes": "ranges", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.SetType(org.apache.cassandra.db.marshal.BytesType)" - }, - { - "keyspace_name": "system", - "table_name": "batches", - "column_name": "id", - "column_name_bytes": "id", - "component_index": null, - "type": "partition_key", - "validator": "org.apache.cassandra.db.marshal.TimeUUIDType" - }, - { - "keyspace_name": "system", - "table_name": "batches", - "column_name": "mutations", - "column_name_bytes": "mutations", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.ListType(org.apache.cassandra.db.marshal.BytesType)" - }, - { - "keyspace_name": "system", - "table_name": "batches", - "column_name": "version", - "column_name_bytes": "version", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.Int32Type" - }, - { - "keyspace_name": "system", - "table_name": "batchlog", - "column_name": "data", - "column_name_bytes": "data", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.BytesType" - }, - { - "keyspace_name": "system", - "table_name": "batchlog", - "column_name": "id", - "column_name_bytes": "id", - "component_index": null, - "type": "partition_key", - "validator": "org.apache.cassandra.db.marshal.UUIDType" - }, - { - "keyspace_name": "system", - "table_name": "batchlog", - "column_name": "version", - "column_name_bytes": "version", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.Int32Type" - }, - { - "keyspace_name": "system", - "table_name": "batchlog", - "column_name": "written_at", - "column_name_bytes": "written_at", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.TimestampType" - }, - { - "keyspace_name": "system", - "table_name": "built_materialized_views", - "column_name": "keyspace_name", - "column_name_bytes": "keyspace_name", - "component_index": null, - "type": "partition_key", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system", - "table_name": "built_materialized_views", - "column_name": "view_name", - "column_name_bytes": "view_name", - "component_index": 0, - "type": "clustering", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system", - "table_name": "compaction_history", - "column_name": "bytes_in", - "column_name_bytes": "bytes_in", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.LongType" - }, - { - "keyspace_name": "system", - "table_name": "compaction_history", - "column_name": "bytes_out", - "column_name_bytes": "bytes_out", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.LongType" - }, - { - "keyspace_name": "system", - "table_name": "compaction_history", - "column_name": "columnfamily_name", - "column_name_bytes": "columnfamily_name", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system", - "table_name": "compaction_history", - "column_name": "compacted_at", - "column_name_bytes": "compacted_at", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.TimestampType" - }, - { - "keyspace_name": "system", - "table_name": "compaction_history", - "column_name": "id", - "column_name_bytes": "id", - "component_index": null, - "type": "partition_key", - "validator": "org.apache.cassandra.db.marshal.UUIDType" - }, - { - "keyspace_name": "system", - "table_name": "compaction_history", - "column_name": "keyspace_name", - "column_name_bytes": "keyspace_name", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system", - "table_name": "compaction_history", - "column_name": "rows_merged", - "column_name_bytes": "rows_merged", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.MapType(org.apache.cassandra.db.marshal.Int32Type,org.apache.cassandra.db.marshal.LongType)" - }, - { - "keyspace_name": "system", - "table_name": "hints", - "column_name": "hint_id", - "column_name_bytes": "hint_id", - "component_index": 0, - "type": "clustering", - "validator": "org.apache.cassandra.db.marshal.TimeUUIDType" - }, - { - "keyspace_name": "system", - "table_name": "hints", - "column_name": "message_version", - "column_name_bytes": "message_version", - "component_index": 1, - "type": "clustering", - "validator": "org.apache.cassandra.db.marshal.Int32Type" - }, - { - "keyspace_name": "system", - "table_name": "hints", - "column_name": "mutation", - "column_name_bytes": "mutation", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.BytesType" - }, - { - "keyspace_name": "system", - "table_name": "hints", - "column_name": "target_id", - "column_name_bytes": "target_id", - "component_index": null, - "type": "partition_key", - "validator": "org.apache.cassandra.db.marshal.UUIDType" - }, - { - "keyspace_name": "system", - "table_name": "local", - "column_name": "bootstrapped", - "column_name_bytes": "bootstrapped", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system", - "table_name": "local", - "column_name": "broadcast_address", - "column_name_bytes": "broadcast_address", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.InetAddressType" - }, - { - "keyspace_name": "system", - "table_name": "local", - "column_name": "cluster_name", - "column_name_bytes": "cluster_name", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system", - "table_name": "local", - "column_name": "cql_version", - "column_name_bytes": "cql_version", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system", - "table_name": "local", - "column_name": "data_center", - "column_name_bytes": "data_center", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system", - "table_name": "local", - "column_name": "gossip_generation", - "column_name_bytes": "gossip_generation", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.Int32Type" - }, - { - "keyspace_name": "system", - "table_name": "local", - "column_name": "host_id", - "column_name_bytes": "host_id", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UUIDType" - }, - { - "keyspace_name": "system", - "table_name": "local", - "column_name": "key", - "column_name_bytes": "key", - "component_index": null, - "type": "partition_key", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system", - "table_name": "local", - "column_name": "listen_address", - "column_name_bytes": "listen_address", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.InetAddressType" - }, - { - "keyspace_name": "system", - "table_name": "local", - "column_name": "native_protocol_version", - "column_name_bytes": "native_protocol_version", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system", - "table_name": "local", - "column_name": "partitioner", - "column_name_bytes": "partitioner", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system", - "table_name": "local", - "column_name": "rack", - "column_name_bytes": "rack", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system", - "table_name": "local", - "column_name": "release_version", - "column_name_bytes": "release_version", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system", - "table_name": "local", - "column_name": "rpc_address", - "column_name_bytes": "rpc_address", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.InetAddressType" - }, - { - "keyspace_name": "system", - "table_name": "local", - "column_name": "schema_version", - "column_name_bytes": "schema_version", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UUIDType" - }, - { - "keyspace_name": "system", - "table_name": "local", - "column_name": "thrift_version", - "column_name_bytes": "thrift_version", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system", - "table_name": "local", - "column_name": "tokens", - "column_name_bytes": "tokens", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.SetType(org.apache.cassandra.db.marshal.UTF8Type)" - }, - { - "keyspace_name": "system", - "table_name": "local", - "column_name": "truncated_at", - "column_name_bytes": "truncated_at", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.MapType(org.apache.cassandra.db.marshal.UUIDType,org.apache.cassandra.db.marshal.BytesType)" - }, - { - "keyspace_name": "system", - "table_name": "materialized_views_builds_in_progress", - "column_name": "generation_number", - "column_name_bytes": "generation_number", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.Int32Type" - }, - { - "keyspace_name": "system", - "table_name": "materialized_views_builds_in_progress", - "column_name": "keyspace_name", - "column_name_bytes": "keyspace_name", - "component_index": null, - "type": "partition_key", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system", - "table_name": "materialized_views_builds_in_progress", - "column_name": "last_token", - "column_name_bytes": "last_token", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system", - "table_name": "materialized_views_builds_in_progress", - "column_name": "view_name", - "column_name_bytes": "view_name", - "component_index": 0, - "type": "clustering", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system", - "table_name": "paxos", - "column_name": "cf_id", - "column_name_bytes": "cf_id", - "component_index": 0, - "type": "clustering", - "validator": "org.apache.cassandra.db.marshal.UUIDType" - }, - { - "keyspace_name": "system", - "table_name": "paxos", - "column_name": "in_progress_ballot", - "column_name_bytes": "in_progress_ballot", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.TimeUUIDType" - }, - { - "keyspace_name": "system", - "table_name": "paxos", - "column_name": "most_recent_commit", - "column_name_bytes": "most_recent_commit", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.BytesType" - }, - { - "keyspace_name": "system", - "table_name": "paxos", - "column_name": "most_recent_commit_at", - "column_name_bytes": "most_recent_commit_at", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.TimeUUIDType" - }, - { - "keyspace_name": "system", - "table_name": "paxos", - "column_name": "most_recent_commit_version", - "column_name_bytes": "most_recent_commit_version", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.Int32Type" - }, - { - "keyspace_name": "system", - "table_name": "paxos", - "column_name": "proposal", - "column_name_bytes": "proposal", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.BytesType" - }, - { - "keyspace_name": "system", - "table_name": "paxos", - "column_name": "proposal_ballot", - "column_name_bytes": "proposal_ballot", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.TimeUUIDType" - }, - { - "keyspace_name": "system", - "table_name": "paxos", - "column_name": "proposal_version", - "column_name_bytes": "proposal_version", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.Int32Type" - }, - { - "keyspace_name": "system", - "table_name": "paxos", - "column_name": "row_key", - "column_name_bytes": "row_key", - "component_index": null, - "type": "partition_key", - "validator": "org.apache.cassandra.db.marshal.BytesType" - }, - { - "keyspace_name": "system", - "table_name": "peer_events", - "column_name": "hints_dropped", - "column_name_bytes": "hints_dropped", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.MapType(org.apache.cassandra.db.marshal.UUIDType,org.apache.cassandra.db.marshal.Int32Type)" - }, - { - "keyspace_name": "system", - "table_name": "peer_events", - "column_name": "peer", - "column_name_bytes": "peer", - "component_index": null, - "type": "partition_key", - "validator": "org.apache.cassandra.db.marshal.InetAddressType" - }, - { - "keyspace_name": "system", - "table_name": "peers", - "column_name": "data_center", - "column_name_bytes": "data_center", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system", - "table_name": "peers", - "column_name": "host_id", - "column_name_bytes": "host_id", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UUIDType" - }, - { - "keyspace_name": "system", - "table_name": "peers", - "column_name": "peer", - "column_name_bytes": "peer", - "component_index": null, - "type": "partition_key", - "validator": "org.apache.cassandra.db.marshal.InetAddressType" - }, - { - "keyspace_name": "system", - "table_name": "peers", - "column_name": "preferred_ip", - "column_name_bytes": "preferred_ip", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.InetAddressType" - }, - { - "keyspace_name": "system", - "table_name": "peers", - "column_name": "rack", - "column_name_bytes": "rack", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system", - "table_name": "peers", - "column_name": "release_version", - "column_name_bytes": "release_version", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system", - "table_name": "peers", - "column_name": "rpc_address", - "column_name_bytes": "rpc_address", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.InetAddressType" - }, - { - "keyspace_name": "system", - "table_name": "peers", - "column_name": "schema_version", - "column_name_bytes": "schema_version", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UUIDType" - }, - { - "keyspace_name": "system", - "table_name": "peers", - "column_name": "tokens", - "column_name_bytes": "tokens", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.SetType(org.apache.cassandra.db.marshal.UTF8Type)" - }, - { - "keyspace_name": "system", - "table_name": "range_xfers", - "column_name": "requested_at", - "column_name_bytes": "requested_at", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.TimestampType" - }, - { - "keyspace_name": "system", - "table_name": "range_xfers", - "column_name": "token_bytes", - "column_name_bytes": "token_bytes", - "component_index": null, - "type": "partition_key", - "validator": "org.apache.cassandra.db.marshal.BytesType" - }, - { - "keyspace_name": "system", - "table_name": "size_estimates", - "column_name": "keyspace_name", - "column_name_bytes": "keyspace_name", - "component_index": null, - "type": "partition_key", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system", - "table_name": "size_estimates", - "column_name": "mean_partition_size", - "column_name_bytes": "mean_partition_size", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.LongType" - }, - { - "keyspace_name": "system", - "table_name": "size_estimates", - "column_name": "partitions_count", - "column_name_bytes": "partitions_count", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.LongType" - }, - { - "keyspace_name": "system", - "table_name": "size_estimates", - "column_name": "range_end", - "column_name_bytes": "range_end", - "component_index": 2, - "type": "clustering", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system", - "table_name": "size_estimates", - "column_name": "range_start", - "column_name_bytes": "range_start", - "component_index": 1, - "type": "clustering", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system", - "table_name": "size_estimates", - "column_name": "table_name", - "column_name_bytes": "table_name", - "component_index": 0, - "type": "clustering", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system", - "table_name": "sstable_activity", - "column_name": "columnfamily_name", - "column_name_bytes": "columnfamily_name", - "component_index": 1, - "type": "partition_key", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system", - "table_name": "sstable_activity", - "column_name": "generation", - "column_name_bytes": "generation", - "component_index": 2, - "type": "partition_key", - "validator": "org.apache.cassandra.db.marshal.Int32Type" - }, - { - "keyspace_name": "system", - "table_name": "sstable_activity", - "column_name": "keyspace_name", - "column_name_bytes": "keyspace_name", - "component_index": 0, - "type": "partition_key", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system", - "table_name": "sstable_activity", - "column_name": "rate_120m", - "column_name_bytes": "rate_120m", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.DoubleType" - }, - { - "keyspace_name": "system", - "table_name": "sstable_activity", - "column_name": "rate_15m", - "column_name_bytes": "rate_15m", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.DoubleType" - }, - { - "keyspace_name": "system_traces", - "table_name": "events", - "column_name": "activity", - "column_name_bytes": "activity", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_traces", - "table_name": "events", - "column_name": "event_id", - "column_name_bytes": "event_id", - "component_index": 0, - "type": "clustering", - "validator": "org.apache.cassandra.db.marshal.TimeUUIDType" - }, - { - "keyspace_name": "system_traces", - "table_name": "events", - "column_name": "session_id", - "column_name_bytes": "session_id", - "component_index": null, - "type": "partition_key", - "validator": "org.apache.cassandra.db.marshal.UUIDType" - }, - { - "keyspace_name": "system_traces", - "table_name": "events", - "column_name": "source", - "column_name_bytes": "source", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.InetAddressType" - }, - { - "keyspace_name": "system_traces", - "table_name": "events", - "column_name": "source_elapsed", - "column_name_bytes": "source_elapsed", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.Int32Type" - }, - { - "keyspace_name": "system_traces", - "table_name": "events", - "column_name": "thread", - "column_name_bytes": "thread", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_traces", - "table_name": "sessions", - "column_name": "client", - "column_name_bytes": "client", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.InetAddressType" - }, - { - "keyspace_name": "system_traces", - "table_name": "sessions", - "column_name": "command", - "column_name_bytes": "command", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_traces", - "table_name": "sessions", - "column_name": "coordinator", - "column_name_bytes": "coordinator", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.InetAddressType" - }, - { - "keyspace_name": "system_traces", - "table_name": "sessions", - "column_name": "duration", - "column_name_bytes": "duration", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.Int32Type" - }, - { - "keyspace_name": "system_traces", - "table_name": "sessions", - "column_name": "parameters", - "column_name_bytes": "parameters", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.MapType(org.apache.cassandra.db.marshal.UTF8Type,org.apache.cassandra.db.marshal.UTF8Type)" - }, - { - "keyspace_name": "system_traces", - "table_name": "sessions", - "column_name": "request", - "column_name_bytes": "request", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.UTF8Type" - }, - { - "keyspace_name": "system_traces", - "table_name": "sessions", - "column_name": "session_id", - "column_name_bytes": "session_id", - "component_index": null, - "type": "partition_key", - "validator": "org.apache.cassandra.db.marshal.UUIDType" - }, - { - "keyspace_name": "system_traces", - "table_name": "sessions", - "column_name": "started_at", - "column_name_bytes": "started_at", - "component_index": null, - "type": "regular", - "validator": "org.apache.cassandra.db.marshal.TimestampType" - } - ], - "SELECT * FROM system_schema.types": [ - - ], - "SELECT * FROM system_schema.functions": [ - - ], - "SELECT * FROM system_schema.aggregates": [ - - ], - "SELECT * FROM system_schema.indexes": [ - - ], - "SELECT * FROM system_schema.materialized_views": [ - - ] -} \ No newline at end of file diff --git a/spec/cassandra/cluster/schema/fetchers/3.0.0-beta2-schema.cql b/spec/cassandra/cluster/schema/fetchers/3.0.0-beta2-schema.cql deleted file mode 100644 index 3b21b80e5..000000000 --- a/spec/cassandra/cluster/schema/fetchers/3.0.0-beta2-schema.cql +++ /dev/null @@ -1,760 +0,0 @@ -CREATE KEYSPACE system_auth WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'} AND durable_writes = true; - -CREATE TABLE system_auth.resource_role_permissons_index ( - resource text, - role text, - PRIMARY KEY (resource, role) -) -WITH bloom_filter_fp_chance = 0.01 - AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} - AND comment = 'index of db roles with permissions granted on a resource' - AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} - AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} - AND dclocal_read_repair_chance = 0.0 - AND default_time_to_live = 0 - AND gc_grace_seconds = 7776000 - AND max_index_interval = 2048 - AND memtable_flush_period_in_ms = 3600000 - AND min_index_interval = 128 - AND read_repair_chance = 0.0 - AND speculative_retry = '99PERCENTILE'; - -CREATE TABLE system_auth.role_members ( - role text, - member text, - PRIMARY KEY (role, member) -) -WITH bloom_filter_fp_chance = 0.01 - AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} - AND comment = 'role memberships lookup table' - AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} - AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} - AND dclocal_read_repair_chance = 0.0 - AND default_time_to_live = 0 - AND gc_grace_seconds = 7776000 - AND max_index_interval = 2048 - AND memtable_flush_period_in_ms = 3600000 - AND min_index_interval = 128 - AND read_repair_chance = 0.0 - AND speculative_retry = '99PERCENTILE'; - -CREATE TABLE system_auth.role_permissions ( - role text, - resource text, - permissions set, - PRIMARY KEY (role, resource) -) -WITH bloom_filter_fp_chance = 0.01 - AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} - AND comment = 'permissions granted to db roles' - AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} - AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} - AND dclocal_read_repair_chance = 0.0 - AND default_time_to_live = 0 - AND gc_grace_seconds = 7776000 - AND max_index_interval = 2048 - AND memtable_flush_period_in_ms = 3600000 - AND min_index_interval = 128 - AND read_repair_chance = 0.0 - AND speculative_retry = '99PERCENTILE'; - -CREATE TABLE system_auth.roles ( - role text PRIMARY KEY, - can_login boolean, - is_superuser boolean, - member_of set, - salted_hash text -) -WITH bloom_filter_fp_chance = 0.01 - AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} - AND comment = 'role definitions' - AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} - AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} - AND dclocal_read_repair_chance = 0.0 - AND default_time_to_live = 0 - AND gc_grace_seconds = 7776000 - AND max_index_interval = 2048 - AND memtable_flush_period_in_ms = 3600000 - AND min_index_interval = 128 - AND read_repair_chance = 0.0 - AND speculative_retry = '99PERCENTILE'; - -CREATE KEYSPACE system_schema WITH replication = {'class': 'LocalStrategy'} AND durable_writes = true; - -CREATE TABLE system_schema.aggregates ( - keyspace_name text, - aggregate_name text, - signature frozen >, - argument_types frozen >, - final_func text, - initcond blob, - return_type text, - state_func text, - state_type text, - PRIMARY KEY (keyspace_name, aggregate_name, signature) -) -WITH bloom_filter_fp_chance = 0.01 - AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} - AND comment = 'user defined aggregate definitions' - AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} - AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} - AND dclocal_read_repair_chance = 0.0 - AND default_time_to_live = 0 - AND gc_grace_seconds = 604800 - AND max_index_interval = 2048 - AND memtable_flush_period_in_ms = 3600000 - AND min_index_interval = 128 - AND read_repair_chance = 0.0 - AND speculative_retry = '99PERCENTILE'; - -CREATE TABLE system_schema.columns ( - keyspace_name text, - table_name text, - column_name text, - column_name_bytes blob, - component_index int, - type text, - validator text, - PRIMARY KEY (keyspace_name, table_name, column_name) -) -WITH bloom_filter_fp_chance = 0.01 - AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} - AND comment = 'column definitions' - AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} - AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} - AND dclocal_read_repair_chance = 0.0 - AND default_time_to_live = 0 - AND gc_grace_seconds = 604800 - AND max_index_interval = 2048 - AND memtable_flush_period_in_ms = 3600000 - AND min_index_interval = 128 - AND read_repair_chance = 0.0 - AND speculative_retry = '99PERCENTILE'; - -CREATE TABLE system_schema.dropped_columns ( - keyspace_name text, - table_name text, - column_name text, - dropped_time timestamp, - type text, - PRIMARY KEY (keyspace_name, table_name, column_name) -) -WITH bloom_filter_fp_chance = 0.01 - AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} - AND comment = 'dropped column registry' - AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} - AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} - AND dclocal_read_repair_chance = 0.0 - AND default_time_to_live = 0 - AND gc_grace_seconds = 604800 - AND max_index_interval = 2048 - AND memtable_flush_period_in_ms = 3600000 - AND min_index_interval = 128 - AND read_repair_chance = 0.0 - AND speculative_retry = '99PERCENTILE'; - -CREATE TABLE system_schema.functions ( - keyspace_name text, - function_name text, - signature frozen >, - argument_names frozen >, - argument_types frozen >, - body text, - called_on_null_input boolean, - language text, - return_type text, - PRIMARY KEY (keyspace_name, function_name, signature) -) -WITH bloom_filter_fp_chance = 0.01 - AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} - AND comment = 'user defined function definitions' - AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} - AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} - AND dclocal_read_repair_chance = 0.0 - AND default_time_to_live = 0 - AND gc_grace_seconds = 604800 - AND max_index_interval = 2048 - AND memtable_flush_period_in_ms = 3600000 - AND min_index_interval = 128 - AND read_repair_chance = 0.0 - AND speculative_retry = '99PERCENTILE'; - -CREATE TABLE system_schema.indexes ( - keyspace_name text, - table_name text, - index_name text, - index_type text, - options frozen >, - target_columns frozen >, - target_type text, - PRIMARY KEY (keyspace_name, table_name, index_name) -) -WITH bloom_filter_fp_chance = 0.01 - AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} - AND comment = 'secondary index definitions' - AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} - AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} - AND dclocal_read_repair_chance = 0.0 - AND default_time_to_live = 0 - AND gc_grace_seconds = 604800 - AND max_index_interval = 2048 - AND memtable_flush_period_in_ms = 3600000 - AND min_index_interval = 128 - AND read_repair_chance = 0.0 - AND speculative_retry = '99PERCENTILE'; - -CREATE TABLE system_schema.keyspaces ( - keyspace_name text PRIMARY KEY, - durable_writes boolean, - replication frozen > -) -WITH bloom_filter_fp_chance = 0.01 - AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} - AND comment = 'keyspace definitions' - AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} - AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} - AND dclocal_read_repair_chance = 0.0 - AND default_time_to_live = 0 - AND gc_grace_seconds = 604800 - AND max_index_interval = 2048 - AND memtable_flush_period_in_ms = 3600000 - AND min_index_interval = 128 - AND read_repair_chance = 0.0 - AND speculative_retry = '99PERCENTILE'; - -CREATE TABLE system_schema.materialized_views ( - keyspace_name text, - table_name text, - view_name text, - clustering_columns frozen >, - included_columns frozen >, - target_columns frozen >, - PRIMARY KEY (keyspace_name, table_name, view_name) -) -WITH bloom_filter_fp_chance = 0.01 - AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} - AND comment = 'materialized views definitions' - AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} - AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} - AND dclocal_read_repair_chance = 0.0 - AND default_time_to_live = 0 - AND gc_grace_seconds = 604800 - AND max_index_interval = 2048 - AND memtable_flush_period_in_ms = 3600000 - AND min_index_interval = 128 - AND read_repair_chance = 0.0 - AND speculative_retry = '99PERCENTILE'; - -CREATE TABLE system_schema.tables ( - keyspace_name text, - table_name text, - bloom_filter_fp_chance double, - caching frozen >, - comment text, - compaction frozen >, - compression frozen >, - dclocal_read_repair_chance double, - default_time_to_live int, - extensions frozen >, - flags frozen >, - gc_grace_seconds int, - id uuid, - max_index_interval int, - memtable_flush_period_in_ms int, - min_index_interval int, - read_repair_chance double, - speculative_retry text, - PRIMARY KEY (keyspace_name, table_name) -) -WITH bloom_filter_fp_chance = 0.01 - AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} - AND comment = 'table definitions' - AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} - AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} - AND dclocal_read_repair_chance = 0.0 - AND default_time_to_live = 0 - AND gc_grace_seconds = 604800 - AND max_index_interval = 2048 - AND memtable_flush_period_in_ms = 3600000 - AND min_index_interval = 128 - AND read_repair_chance = 0.0 - AND speculative_retry = '99PERCENTILE'; - -CREATE TABLE system_schema.triggers ( - keyspace_name text, - table_name text, - trigger_name text, - trigger_options frozen >, - PRIMARY KEY (keyspace_name, table_name, trigger_name) -) -WITH bloom_filter_fp_chance = 0.01 - AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} - AND comment = 'trigger definitions' - AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} - AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} - AND dclocal_read_repair_chance = 0.0 - AND default_time_to_live = 0 - AND gc_grace_seconds = 604800 - AND max_index_interval = 2048 - AND memtable_flush_period_in_ms = 3600000 - AND min_index_interval = 128 - AND read_repair_chance = 0.0 - AND speculative_retry = '99PERCENTILE'; - -CREATE TABLE system_schema.types ( - keyspace_name text, - type_name text, - field_names frozen >, - field_types frozen >, - PRIMARY KEY (keyspace_name, type_name) -) -WITH bloom_filter_fp_chance = 0.01 - AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} - AND comment = 'user defined type definitions' - AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} - AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} - AND dclocal_read_repair_chance = 0.0 - AND default_time_to_live = 0 - AND gc_grace_seconds = 604800 - AND max_index_interval = 2048 - AND memtable_flush_period_in_ms = 3600000 - AND min_index_interval = 128 - AND read_repair_chance = 0.0 - AND speculative_retry = '99PERCENTILE'; - -CREATE KEYSPACE system_distributed WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '3'} AND durable_writes = true; - -CREATE TABLE system_distributed.parent_repair_history ( - parent_id timeuuid PRIMARY KEY, - columnfamily_names set, - exception_message text, - exception_stacktrace text, - finished_at timestamp, - keyspace_name text, - requested_ranges set, - started_at timestamp, - successful_ranges set -) -WITH bloom_filter_fp_chance = 0.01 - AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} - AND comment = 'Repair history' - AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} - AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} - AND dclocal_read_repair_chance = 0.0 - AND default_time_to_live = 0 - AND gc_grace_seconds = 0 - AND max_index_interval = 2048 - AND memtable_flush_period_in_ms = 3600000 - AND min_index_interval = 128 - AND read_repair_chance = 0.0 - AND speculative_retry = '99PERCENTILE'; - -CREATE TABLE system_distributed.repair_history ( - keyspace_name text, - columnfamily_name text, - id timeuuid, - coordinator inet, - exception_message text, - exception_stacktrace text, - finished_at timestamp, - parent_id timeuuid, - participants set, - range_begin text, - range_end text, - started_at timestamp, - status text, - PRIMARY KEY ((keyspace_name, columnfamily_name), id) -) -WITH bloom_filter_fp_chance = 0.01 - AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} - AND comment = 'Repair history' - AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} - AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} - AND dclocal_read_repair_chance = 0.0 - AND default_time_to_live = 0 - AND gc_grace_seconds = 0 - AND max_index_interval = 2048 - AND memtable_flush_period_in_ms = 3600000 - AND min_index_interval = 128 - AND read_repair_chance = 0.0 - AND speculative_retry = '99PERCENTILE'; - -CREATE KEYSPACE system WITH replication = {'class': 'LocalStrategy'} AND durable_writes = true; - -CREATE TABLE system."IndexInfo" ( - table_name text, - index_name text, - value 'org.apache.cassandra.db.marshal.EmptyType', - PRIMARY KEY (table_name, index_name) -) -WITH COMPACT STORAGE - AND bloom_filter_fp_chance = 0.01 - AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} - AND comment = 'built column indexes' - AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} - AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} - AND dclocal_read_repair_chance = 0.0 - AND default_time_to_live = 0 - AND gc_grace_seconds = 0 - AND max_index_interval = 2048 - AND memtable_flush_period_in_ms = 3600000 - AND min_index_interval = 128 - AND read_repair_chance = 0.0 - AND speculative_retry = '99PERCENTILE'; - -CREATE TABLE system.available_ranges ( - keyspace_name text PRIMARY KEY, - ranges set -) -WITH bloom_filter_fp_chance = 0.01 - AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} - AND comment = 'available keyspace/ranges during bootstrap/replace that are ready to be served' - AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} - AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} - AND dclocal_read_repair_chance = 0.0 - AND default_time_to_live = 0 - AND gc_grace_seconds = 0 - AND max_index_interval = 2048 - AND memtable_flush_period_in_ms = 3600000 - AND min_index_interval = 128 - AND read_repair_chance = 0.0 - AND speculative_retry = '99PERCENTILE'; - -CREATE TABLE system.batches ( - id timeuuid PRIMARY KEY, - mutations list, - version int -) -WITH bloom_filter_fp_chance = 0.01 - AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} - AND comment = 'batches awaiting replay' - AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '2'} - AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} - AND dclocal_read_repair_chance = 0.0 - AND default_time_to_live = 0 - AND gc_grace_seconds = 0 - AND max_index_interval = 2048 - AND memtable_flush_period_in_ms = 3600000 - AND min_index_interval = 128 - AND read_repair_chance = 0.0 - AND speculative_retry = '99PERCENTILE'; - -CREATE TABLE system.batchlog ( - id uuid PRIMARY KEY, - data blob, - version int, - written_at timestamp -) -WITH bloom_filter_fp_chance = 0.01 - AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} - AND comment = '*DEPRECATED* batchlog entries' - AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '2'} - AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} - AND dclocal_read_repair_chance = 0.0 - AND default_time_to_live = 0 - AND gc_grace_seconds = 0 - AND max_index_interval = 2048 - AND memtable_flush_period_in_ms = 3600000 - AND min_index_interval = 128 - AND read_repair_chance = 0.0 - AND speculative_retry = '99PERCENTILE'; - -CREATE TABLE system.built_materialized_views ( - keyspace_name text, - view_name text, - PRIMARY KEY (keyspace_name, view_name) -) -WITH bloom_filter_fp_chance = 0.01 - AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} - AND comment = 'built materialized views' - AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} - AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} - AND dclocal_read_repair_chance = 0.0 - AND default_time_to_live = 0 - AND gc_grace_seconds = 0 - AND max_index_interval = 2048 - AND memtable_flush_period_in_ms = 3600000 - AND min_index_interval = 128 - AND read_repair_chance = 0.0 - AND speculative_retry = '99PERCENTILE'; - -CREATE TABLE system.compaction_history ( - id uuid PRIMARY KEY, - bytes_in bigint, - bytes_out bigint, - columnfamily_name text, - compacted_at timestamp, - keyspace_name text, - rows_merged map -) -WITH bloom_filter_fp_chance = 0.01 - AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} - AND comment = 'week-long compaction history' - AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} - AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} - AND dclocal_read_repair_chance = 0.0 - AND default_time_to_live = 604800 - AND gc_grace_seconds = 0 - AND max_index_interval = 2048 - AND memtable_flush_period_in_ms = 3600000 - AND min_index_interval = 128 - AND read_repair_chance = 0.0 - AND speculative_retry = '99PERCENTILE'; - -CREATE TABLE system.hints ( - target_id uuid, - hint_id timeuuid, - message_version int, - mutation blob, - PRIMARY KEY (target_id, hint_id, message_version) -) -WITH COMPACT STORAGE - AND bloom_filter_fp_chance = 0.01 - AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} - AND comment = '*DEPRECATED* hints awaiting delivery' - AND compaction = {'class': 'SizeTieredCompactionStrategy', 'enabled': 'false', 'max_threshold': '32', 'min_threshold': '4'} - AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} - AND dclocal_read_repair_chance = 0.0 - AND default_time_to_live = 0 - AND gc_grace_seconds = 0 - AND max_index_interval = 2048 - AND memtable_flush_period_in_ms = 3600000 - AND min_index_interval = 128 - AND read_repair_chance = 0.0 - AND speculative_retry = '99PERCENTILE'; - -CREATE TABLE system.local ( - key text PRIMARY KEY, - bootstrapped text, - broadcast_address inet, - cluster_name text, - cql_version text, - data_center text, - gossip_generation int, - host_id uuid, - listen_address inet, - native_protocol_version text, - partitioner text, - rack text, - release_version text, - rpc_address inet, - schema_version uuid, - thrift_version text, - tokens set, - truncated_at map -) -WITH bloom_filter_fp_chance = 0.01 - AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} - AND comment = 'information about the local node' - AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} - AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} - AND dclocal_read_repair_chance = 0.0 - AND default_time_to_live = 0 - AND gc_grace_seconds = 0 - AND max_index_interval = 2048 - AND memtable_flush_period_in_ms = 3600000 - AND min_index_interval = 128 - AND read_repair_chance = 0.0 - AND speculative_retry = '99PERCENTILE'; - -CREATE TABLE system.materialized_views_builds_in_progress ( - keyspace_name text, - view_name text, - generation_number int, - last_token text, - PRIMARY KEY (keyspace_name, view_name) -) -WITH bloom_filter_fp_chance = 0.01 - AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} - AND comment = 'materialized views builds current progress' - AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} - AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} - AND dclocal_read_repair_chance = 0.0 - AND default_time_to_live = 0 - AND gc_grace_seconds = 0 - AND max_index_interval = 2048 - AND memtable_flush_period_in_ms = 3600000 - AND min_index_interval = 128 - AND read_repair_chance = 0.0 - AND speculative_retry = '99PERCENTILE'; - -CREATE TABLE system.paxos ( - row_key blob, - cf_id uuid, - in_progress_ballot timeuuid, - most_recent_commit blob, - most_recent_commit_at timeuuid, - most_recent_commit_version int, - proposal blob, - proposal_ballot timeuuid, - proposal_version int, - PRIMARY KEY (row_key, cf_id) -) -WITH bloom_filter_fp_chance = 0.01 - AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} - AND comment = 'in-progress paxos proposals' - AND compaction = {'class': 'LeveledCompactionStrategy'} - AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} - AND dclocal_read_repair_chance = 0.0 - AND default_time_to_live = 0 - AND gc_grace_seconds = 0 - AND max_index_interval = 2048 - AND memtable_flush_period_in_ms = 3600000 - AND min_index_interval = 128 - AND read_repair_chance = 0.0 - AND speculative_retry = '99PERCENTILE'; - -CREATE TABLE system.peer_events ( - peer inet PRIMARY KEY, - hints_dropped map -) -WITH bloom_filter_fp_chance = 0.01 - AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} - AND comment = 'events related to peers' - AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} - AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} - AND dclocal_read_repair_chance = 0.0 - AND default_time_to_live = 0 - AND gc_grace_seconds = 0 - AND max_index_interval = 2048 - AND memtable_flush_period_in_ms = 3600000 - AND min_index_interval = 128 - AND read_repair_chance = 0.0 - AND speculative_retry = '99PERCENTILE'; - -CREATE TABLE system.peers ( - peer inet PRIMARY KEY, - data_center text, - host_id uuid, - preferred_ip inet, - rack text, - release_version text, - rpc_address inet, - schema_version uuid, - tokens set -) -WITH bloom_filter_fp_chance = 0.01 - AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} - AND comment = 'information about known peers in the cluster' - AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} - AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} - AND dclocal_read_repair_chance = 0.0 - AND default_time_to_live = 0 - AND gc_grace_seconds = 0 - AND max_index_interval = 2048 - AND memtable_flush_period_in_ms = 3600000 - AND min_index_interval = 128 - AND read_repair_chance = 0.0 - AND speculative_retry = '99PERCENTILE'; - -CREATE TABLE system.range_xfers ( - token_bytes blob PRIMARY KEY, - requested_at timestamp -) -WITH bloom_filter_fp_chance = 0.01 - AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} - AND comment = 'ranges requested for transfer' - AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} - AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} - AND dclocal_read_repair_chance = 0.0 - AND default_time_to_live = 0 - AND gc_grace_seconds = 0 - AND max_index_interval = 2048 - AND memtable_flush_period_in_ms = 3600000 - AND min_index_interval = 128 - AND read_repair_chance = 0.0 - AND speculative_retry = '99PERCENTILE'; - -CREATE TABLE system.size_estimates ( - keyspace_name text, - table_name text, - range_start text, - range_end text, - mean_partition_size bigint, - partitions_count bigint, - PRIMARY KEY (keyspace_name, table_name, range_start, range_end) -) -WITH bloom_filter_fp_chance = 0.01 - AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} - AND comment = 'per-table primary range size estimates' - AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} - AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} - AND dclocal_read_repair_chance = 0.0 - AND default_time_to_live = 0 - AND gc_grace_seconds = 0 - AND max_index_interval = 2048 - AND memtable_flush_period_in_ms = 3600000 - AND min_index_interval = 128 - AND read_repair_chance = 0.0 - AND speculative_retry = '99PERCENTILE'; - -CREATE TABLE system.sstable_activity ( - keyspace_name text, - columnfamily_name text, - generation int, - rate_120m double, - rate_15m double, - PRIMARY KEY ((keyspace_name, columnfamily_name, generation)) -) -WITH bloom_filter_fp_chance = 0.01 - AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} - AND comment = 'historic sstable read rates' - AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} - AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} - AND dclocal_read_repair_chance = 0.0 - AND default_time_to_live = 0 - AND gc_grace_seconds = 0 - AND max_index_interval = 2048 - AND memtable_flush_period_in_ms = 3600000 - AND min_index_interval = 128 - AND read_repair_chance = 0.0 - AND speculative_retry = '99PERCENTILE'; - -CREATE KEYSPACE system_traces WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '2'} AND durable_writes = true; - -CREATE TABLE system_traces.events ( - session_id uuid, - event_id timeuuid, - activity text, - source inet, - source_elapsed int, - thread text, - PRIMARY KEY (session_id, event_id) -) -WITH bloom_filter_fp_chance = 0.01 - AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} - AND comment = 'tracing events' - AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} - AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} - AND dclocal_read_repair_chance = 0.0 - AND default_time_to_live = 0 - AND gc_grace_seconds = 0 - AND max_index_interval = 2048 - AND memtable_flush_period_in_ms = 3600000 - AND min_index_interval = 128 - AND read_repair_chance = 0.0 - AND speculative_retry = '99PERCENTILE'; - -CREATE TABLE system_traces.sessions ( - session_id uuid PRIMARY KEY, - client inet, - command text, - coordinator inet, - duration int, - parameters map, - request text, - started_at timestamp -) -WITH bloom_filter_fp_chance = 0.01 - AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} - AND comment = 'tracing sessions' - AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} - AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} - AND dclocal_read_repair_chance = 0.0 - AND default_time_to_live = 0 - AND gc_grace_seconds = 0 - AND max_index_interval = 2048 - AND memtable_flush_period_in_ms = 3600000 - AND min_index_interval = 128 - AND read_repair_chance = 0.0 - AND speculative_retry = '99PERCENTILE'; \ No newline at end of file diff --git a/spec/cassandra/cluster/schema/fetchers/3.0.0-data.json b/spec/cassandra/cluster/schema/fetchers/3.0.0-data.json new file mode 100644 index 000000000..33c64c87a --- /dev/null +++ b/spec/cassandra/cluster/schema/fetchers/3.0.0-data.json @@ -0,0 +1,402 @@ +{ + "SELECT * FROM system_schema.keyspaces": [ + { + "keyspace_name": "simplex", + "durable_writes": true, + "replication": { + "class": "org.apache.cassandra.locator.SimpleStrategy", + "replication_factor": "1" + } + } + ], + "SELECT * FROM system_schema.tables": [ + { + "keyspace_name": "simplex", + "table_name": "t1", + "bloom_filter_fp_chance": 0.01, + "caching": { + "keys": "ALL", + "rows_per_partition": "NONE" + }, + "comment": "test table for unit tests", + "compaction": { + "class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", + "max_threshold": "32", + "min_threshold": "4" + }, + "compression": { + "chunk_length_in_kb": "64", + "class": "org.apache.cassandra.io.compress.LZ4Compressor" + }, + "crc_check_chance": 1.0, + "dclocal_read_repair_chance": 0.1, + "default_time_to_live": 0, + "extensions": { + "object_type": "ext table" + }, + "flags": [ + "compound" + ], + "gc_grace_seconds": 7776000, + "id": "5f2fbdad-91f1-3946-bd25-d5da3a5c35ec", + "max_index_interval": 2048, + "memtable_flush_period_in_ms": 42, + "min_index_interval": 128, + "read_repair_chance": 0.0, + "speculative_retry": "99PERCENTILE" + }, + { + "keyspace_name": "simplex", + "table_name": "t2", + "bloom_filter_fp_chance": 0.01, + "caching": { + "keys": "ALL", + "rows_per_partition": "NONE" + }, + "comment": "compact (dense) test table for unit tests", + "compaction": { + "class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", + "max_threshold": "32", + "min_threshold": "4" + }, + "compression": { + "chunk_length_in_kb": "64", + "class": "org.apache.cassandra.io.compress.LZ4Compressor" + }, + "crc_check_chance": 1.0, + "dclocal_read_repair_chance": 0.1, + "default_time_to_live": 0, + "extensions": { + "object_type": "ext table" + }, + "flags": ["dense"], + "gc_grace_seconds": 7776000, + "id": "5f2fbdad-01f1-3946-bd25-d5da3a5c35ec", + "max_index_interval": 2048, + "memtable_flush_period_in_ms": 42, + "min_index_interval": 128, + "read_repair_chance": 0.0, + "speculative_retry": "99PERCENTILE" + }, + { + "keyspace_name": "simplex", + "table_name": "t3", + "bloom_filter_fp_chance": 0.01, + "caching": { + "keys": "ALL", + "rows_per_partition": "NONE" + }, + "comment": "compact test table for unit tests", + "compaction": { + "class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", + "max_threshold": "32", + "min_threshold": "4" + }, + "compression": { + "chunk_length_in_kb": "64", + "class": "org.apache.cassandra.io.compress.LZ4Compressor" + }, + "crc_check_chance": 1.0, + "dclocal_read_repair_chance": 0.1, + "default_time_to_live": 0, + "extensions": { + "object_type": "ext table" + }, + "flags": [], + "gc_grace_seconds": 7776000, + "id": "5f2fbdad-91f1-4946-bd25-d5da3a5c35ec", + "max_index_interval": 2048, + "memtable_flush_period_in_ms": 42, + "min_index_interval": 128, + "read_repair_chance": 0.0, + "speculative_retry": "99PERCENTILE" + }, + { + "keyspace_name": "simplex", + "table_name": "t4", + "bloom_filter_fp_chance": 0.01, + "caching": { + "keys": "ALL", + "rows_per_partition": "NONE" + }, + "comment": "compact (dense) test table for unit tests", + "compaction": { + "class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", + "max_threshold": "32", + "min_threshold": "4" + }, + "compression": { + "chunk_length_in_kb": "64", + "class": "org.apache.cassandra.io.compress.LZ4Compressor" + }, + "crc_check_chance": 1.0, + "dclocal_read_repair_chance": 0.1, + "default_time_to_live": 0, + "extensions": { + "object_type": "ext table" + }, + "flags": ["dense"], + "gc_grace_seconds": 7776000, + "id": "5f2fbdad-91f1-4946-bd35-d5da3a5c35ec", + "max_index_interval": 2048, + "memtable_flush_period_in_ms": 42, + "min_index_interval": 128, + "read_repair_chance": 0.0, + "speculative_retry": "99PERCENTILE" + } + ], + "SELECT * FROM system_schema.columns": [ + { + "keyspace_name": "simplex", + "table_name": "t1", + "column_name": "f1", + "clustering_order": "none", + "column_name_bytes": "f1", + "kind": "partition_key", + "position": 0, + "type": "int" + }, + { + "keyspace_name": "simplex", + "table_name": "t1", + "column_name": "f2", + "clustering_order": "asc", + "column_name_bytes": "f2", + "kind": "clustering", + "position": 0, + "type": "int" + }, + { + "keyspace_name": "simplex", + "table_name": "t1", + "column_name": "f3", + "clustering_order": "none", + "column_name_bytes": "f3", + "kind": "regular", + "position": -1, + "type": "int" + }, + { + "keyspace_name": "simplex", + "table_name": "v1", + "column_name": "f1", + "clustering_order": "none", + "column_name_bytes": "f1", + "kind": "partition_key", + "position": 0, + "type": "int" + }, + { + "keyspace_name": "simplex", + "table_name": "v1", + "column_name": "f2", + "clustering_order": "asc", + "column_name_bytes": "f2", + "kind": "clustering", + "position": 0, + "type": "int" + }, + { + "keyspace_name": "simplex", + "table_name": "v2", + "column_name": "f1", + "clustering_order": "none", + "column_name_bytes": "f1", + "kind": "partition_key", + "position": 0, + "type": "int" + }, + { + "keyspace_name": "simplex", + "table_name": "v2", + "column_name": "f2", + "clustering_order": "asc", + "column_name_bytes": "f2", + "kind": "clustering", + "position": 0, + "type": "int" + }, + { + "keyspace_name": "simplex", + "table_name": "v2", + "column_name": "f3", + "clustering_order": "none", + "column_name_bytes": "f3", + "kind": "regular", + "position": -1, + "type": "int" + }, + { + "keyspace_name": "simplex", + "table_name": "t2", + "column_name": "f1", + "clustering_order": "none", + "column_name_bytes": "f1", + "kind": "partition_key", + "position": 0, + "type": "int" + }, + { + "keyspace_name": "simplex", + "table_name": "t2", + "column_name": "f2", + "clustering_order": "asc", + "column_name_bytes": "f2", + "kind": "clustering", + "position": 0, + "type": "int" + }, + { + "keyspace_name": "simplex", + "table_name": "t2", + "column_name": "f3", + "clustering_order": "none", + "column_name_bytes": "f3", + "kind": "regular", + "position": -1, + "type": "int" + }, + { + "keyspace_name": "simplex", + "table_name": "t3", + "column_name": "f1", + "clustering_order": "none", + "column_name_bytes": "f1", + "kind": "partition_key", + "position": 0, + "type": "int" + }, + { + "keyspace_name": "simplex", + "table_name": "t3", + "column_name": "f2", + "clustering_order": "none", + "column_name_bytes": "f2", + "kind": "regular", + "position": -1, + "type": "int" + }, + { + "keyspace_name": "simplex", + "table_name": "t4", + "column_name": "f1", + "clustering_order": "none", + "column_name_bytes": "f1", + "kind": "partition_key", + "position": 0, + "type": "int" + }, + { + "keyspace_name": "simplex", + "table_name": "t4", + "column_name": "f2", + "clustering_order": "desc", + "column_name_bytes": "f2", + "kind": "clustering", + "position": 0, + "type": "int" + }, + { + "keyspace_name": "simplex", + "table_name": "t4", + "column_name": "f3", + "clustering_order": "none", + "column_name_bytes": "f3", + "kind": "regular", + "position": -1, + "type": "int" + } + ], + "SELECT * FROM system_schema.types": [ + + ], + "SELECT * FROM system_schema.functions": [ + + ], + "SELECT * FROM system_schema.aggregates": [ + + ], + "SELECT * FROM system_schema.indexes": [ + { + "keyspace_name": "simplex", + "table_name": "t1", + "index_name": "ind1", + "kind": "COMPOSITES", + "options": {"target": "f2"} + } + ], + "SELECT * FROM system_schema.views": [ + { + "keyspace_name": "simplex", + "view_name": "v1", + "base_table_id": "5f2fbdad-91f1-3946-bd25-d5da3a5c35ec", + "base_table_name": "t1", + "bloom_filter_fp_chance": 0.01, + "caching": { + "keys": "ALL", + "rows_per_partition": "NONE" + }, + "comment": "test view for unit tests", + "compaction": { + "class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", + "max_threshold": "32", + "min_threshold": "4" + }, + "compression": { + "chunk_length_in_kb": "64", + "class": "org.apache.cassandra.io.compress.LZ4Compressor" + }, + "crc_check_chance": 1.0, + "dclocal_read_repair_chance": 0.1, + "default_time_to_live": 0, + "extensions": { + "object_type": "ext view" + }, + "gc_grace_seconds": 7776000, + "id": "4f2fbdad-91f1-3946-bd25-d5da3a5c35ec", + "include_all_columns": false, + "max_index_interval": 2048, + "memtable_flush_period_in_ms": 42, + "min_index_interval": 128, + "read_repair_chance": 0.0, + "speculative_retry": "99PERCENTILE", + "where_clause": "f2 IS NOT NULL" + }, + { + "keyspace_name": "simplex", + "view_name": "v2", + "base_table_id": "5f2fbdad-91f1-3946-bd25-d5da3a5c35ec", + "base_table_name": "t1", + "bloom_filter_fp_chance": 0.01, + "caching": { + "keys": "ALL", + "rows_per_partition": "NONE" + }, + "comment": "test view for unit tests", + "compaction": { + "class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", + "max_threshold": "32", + "min_threshold": "4" + }, + "compression": { + "chunk_length_in_kb": "64", + "class": "org.apache.cassandra.io.compress.LZ4Compressor" + }, + "crc_check_chance": 1.0, + "dclocal_read_repair_chance": 0.1, + "default_time_to_live": 42, + "extensions": { + "object_type": "ext view" + }, + "gc_grace_seconds": 7776000, + "id": "4f2fbdad-91f1-3946-bd25-d5da3a5c35ed", + "include_all_columns": true, + "max_index_interval": 2048, + "memtable_flush_period_in_ms": 42, + "min_index_interval": 128, + "read_repair_chance": 0.2, + "speculative_retry": "99PERCENTILE", + "where_clause": "f2 IS NOT NULL" + } + ] +} \ No newline at end of file diff --git a/spec/cassandra/cluster/schema/fetchers/3.0.0-schema.cql b/spec/cassandra/cluster/schema/fetchers/3.0.0-schema.cql new file mode 100644 index 000000000..38e03d515 --- /dev/null +++ b/spec/cassandra/cluster/schema/fetchers/3.0.0-schema.cql @@ -0,0 +1,129 @@ +CREATE KEYSPACE simplex WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'} AND durable_writes = true; + +CREATE TABLE simplex."t1" ( + f1 int, + f2 int, + f3 int, + PRIMARY KEY (f1, f2) +) +WITH bloom_filter_fp_chance = 0.01 + AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} + AND comment = 'test table for unit tests' + AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} + AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} + AND crc_check_chance = 1.0 + AND dclocal_read_repair_chance = 0.1 + AND default_time_to_live = 0 + AND gc_grace_seconds = 7776000 + AND max_index_interval = 2048 + AND memtable_flush_period_in_ms = 42 + AND min_index_interval = 128 + AND read_repair_chance = 0.0 + AND speculative_retry = '99PERCENTILE'; + +CREATE INDEX "ind1" ON simplex."t1" (f2); + +CREATE TABLE simplex."t2" ( + f1 int, + f2 int, + f3 int, + PRIMARY KEY (f1, f2) +) +WITH COMPACT STORAGE + AND bloom_filter_fp_chance = 0.01 + AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} + AND comment = 'compact (dense) test table for unit tests' + AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} + AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} + AND crc_check_chance = 1.0 + AND dclocal_read_repair_chance = 0.1 + AND default_time_to_live = 0 + AND gc_grace_seconds = 7776000 + AND max_index_interval = 2048 + AND memtable_flush_period_in_ms = 42 + AND min_index_interval = 128 + AND read_repair_chance = 0.0 + AND speculative_retry = '99PERCENTILE'; + +CREATE TABLE simplex."t3" ( + f1 int PRIMARY KEY, + f2 int +) +WITH COMPACT STORAGE + AND bloom_filter_fp_chance = 0.01 + AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} + AND comment = 'compact test table for unit tests' + AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} + AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} + AND crc_check_chance = 1.0 + AND dclocal_read_repair_chance = 0.1 + AND default_time_to_live = 0 + AND gc_grace_seconds = 7776000 + AND max_index_interval = 2048 + AND memtable_flush_period_in_ms = 42 + AND min_index_interval = 128 + AND read_repair_chance = 0.0 + AND speculative_retry = '99PERCENTILE'; + +CREATE TABLE simplex."t4" ( + f1 int, + f2 int, + f3 int, + PRIMARY KEY (f1, f2) +) +WITH CLUSTERING ORDER BY (f2 DESC) + AND COMPACT STORAGE + AND bloom_filter_fp_chance = 0.01 + AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} + AND comment = 'compact (dense) test table for unit tests' + AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} + AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} + AND crc_check_chance = 1.0 + AND dclocal_read_repair_chance = 0.1 + AND default_time_to_live = 0 + AND gc_grace_seconds = 7776000 + AND max_index_interval = 2048 + AND memtable_flush_period_in_ms = 42 + AND min_index_interval = 128 + AND read_repair_chance = 0.0 + AND speculative_retry = '99PERCENTILE'; + +CREATE MATERIALIZED VIEW simplex."v1" AS +SELECT "f1", "f2" +FROM simplex."t1" +WHERE f2 IS NOT NULL +PRIMARY KEY (("f1"), "f2") +WITH bloom_filter_fp_chance = 0.01 + AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} + AND comment = 'test view for unit tests' + AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} + AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} + AND crc_check_chance = 1.0 + AND dclocal_read_repair_chance = 0.1 + AND default_time_to_live = 0 + AND gc_grace_seconds = 7776000 + AND max_index_interval = 2048 + AND memtable_flush_period_in_ms = 42 + AND min_index_interval = 128 + AND read_repair_chance = 0.0 + AND speculative_retry = '99PERCENTILE'; + +CREATE MATERIALIZED VIEW simplex."v2" AS +SELECT * +FROM simplex."t1" +WHERE f2 IS NOT NULL +PRIMARY KEY (("f1"), "f2") +WITH bloom_filter_fp_chance = 0.01 + AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} + AND comment = 'test view for unit tests' + AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} + AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} + AND crc_check_chance = 1.0 + AND dclocal_read_repair_chance = 0.1 + AND default_time_to_live = 42 + AND gc_grace_seconds = 7776000 + AND max_index_interval = 2048 + AND memtable_flush_period_in_ms = 42 + AND min_index_interval = 128 + AND read_repair_chance = 0.2 + AND speculative_retry = '99PERCENTILE'; \ No newline at end of file diff --git a/spec/cassandra/cluster/schema/fetchers_spec.rb b/spec/cassandra/cluster/schema/fetchers_spec.rb index 5a24a6b0e..9ccce29e3 100644 --- a/spec/cassandra/cluster/schema/fetchers_spec.rb +++ b/spec/cassandra/cluster/schema/fetchers_spec.rb @@ -23,18 +23,20 @@ class Cluster class Schema module Fetchers [ - ['1.2.19', V1_2_x], - ['2.0.16', V2_0_x], - ['2.1.9', V2_1_x], - ['2.2.1', V2_2_x], - # ['3.0.0-beta2', V3_0_x], - ].each do |(version, klass)| - data = JSON.load(File.read(File.dirname(__FILE__) + '/fetchers/' + version + '-data.json')) + ['1.2.19', V1_2_x, FQCNTypeParser], + ['2.0.16', V2_0_x, FQCNTypeParser], + ['2.1.9', V2_1_x, FQCNTypeParser], + ['2.2.1', V2_2_x, FQCNTypeParser], + ['3.0.0', V3_0_x, CQLTypeParser] + ].each do |(version, klass, parser_class)| describe(klass) do + let(:data) { JSON.load(File.read(File.dirname(__FILE__) + '/fetchers/' + version + '-data.json')) } let(:connection) { double('cql protocol handler') } - let(:schema_type_parser) { FQCNTypeParser.new } - let(:cluster_schema) { double('schema') } + let(:schema_type_parser) { + parser_class.new + } + let(:cluster_schema) { double('schema') } subject { klass.new(schema_type_parser, cluster_schema) } before do @@ -45,14 +47,14 @@ module Fetchers before do allow(connection).to receive(:send_request) do |r| case r - when Protocol::QueryRequest - if data.include?(r.cql) - Ione::Future.resolved(Protocol::RowsResultResponse.new(nil, nil, data[r.cql], nil, nil, nil)) + when Protocol::QueryRequest + if data.include?(r.cql) + Ione::Future.resolved(Protocol::RowsResultResponse.new(nil, nil, data[r.cql], nil, nil, nil)) + else + raise "unsupported cql: #{r.cql}" + end else - raise "unsupported cql: #{r.cql}" - end - else - raise "unexpected request: #{r.inspect}" + raise "unexpected request: #{r.inspect}" end end end @@ -64,10 +66,31 @@ module Fetchers parts << keyspace.to_cql keyspace.each_table do |table| parts << table.to_cql + + table.each_index do |index| + parts << index.to_cql + end + end + keyspace.each_materialized_view do |view| + parts << view.to_cql end end cql = parts.join("\n\n") - expect(File.read(File.dirname(__FILE__) + '/fetchers/' + version + '-schema.cql')).to eq(cql) + expect(cql).to eq(File.read(File.dirname(__FILE__) + '/fetchers/' + version + '-schema.cql')) + end + + if klass == V3_0_x + it 'reports table extensions properly' do + keyspaces = subject.fetch(connection).value + table = keyspaces.first.table('t1') + expect(table.options.extensions).to eq({ 'object_type' => 'ext table' }) + end + + it 'reports view extensions properly' do + keyspaces = subject.fetch(connection).value + view = keyspaces.first.materialized_view('v1') + expect(view.options.extensions).to eq({ 'object_type' => 'ext view' }) + end end end end From a46a6e0fda87e68318db8bad7c2fc0ed439cc0f6 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Tue, 29 Mar 2016 13:28:28 -0700 Subject: [PATCH 018/196] Very minor tweaks to expected output for view metadata cucumber test to align with recent code changes. --- features/basics/schema_metadata.feature | 28 ++++++++++++------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/features/basics/schema_metadata.feature b/features/basics/schema_metadata.feature index 39fd61eaf..dabd45d66 100644 --- a/features/basics/schema_metadata.feature +++ b/features/basics/schema_metadata.feature @@ -120,21 +120,21 @@ Feature: Schema Metadata SELECT "f1", "f2" FROM simplex.test_table WHERE f2 IS NOT NULL - PRIMARY KEY(("f1"),"f2") + PRIMARY KEY (("f1"), "f2") WITH bloom_filter_fp_chance = 0.01 - AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} - AND comment = '' - AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} - AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} - AND dclocal_read_repair_chance = 0.1 - AND default_time_to_live = 0 - AND gc_grace_seconds = 864000 - AND max_index_interval = 2048 - AND memtable_flush_period_in_ms = 0 - AND min_index_interval = 128 - AND read_repair_chance = 0.0 - AND speculative_retry = '99PERCENTILE' - AND crc_check_chance = 1.0; + AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} + AND comment = '' + AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} + AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} + AND crc_check_chance = 1.0 + AND dclocal_read_repair_chance = 0.1 + AND default_time_to_live = 0 + AND gc_grace_seconds = 864000 + AND max_index_interval = 2048 + AND memtable_flush_period_in_ms = 0 + AND min_index_interval = 128 + AND read_repair_chance = 0.0 + AND speculative_retry = '99PERCENTILE'; """ @cassandra-version-specific @cassandra-version-2.1 From 7ee62c94af01f3d17dbc0d626ec6bbe7dcfdbdd7 Mon Sep 17 00:00:00 2001 From: Kishan Karunaratne Date: Wed, 30 Mar 2016 10:32:49 -0700 Subject: [PATCH 019/196] [RUBY-168] Integration tests for host broadcast and listen address --- integration/session_test.rb | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/integration/session_test.rb b/integration/session_test.rb index 430991b2c..eeab8e0b2 100644 --- a/integration/session_test.rb +++ b/integration/session_test.rb @@ -751,4 +751,25 @@ def test_unset_values cluster && cluster.close end + # Test for host broadcast_address and listen_address + # + # test_host_broadcast_listen_address tests that broadcast_address and listen_address metadata are able to be retrieved + # from a host, when they are available. + # + # @since 3.0.0 + # @jira_ticket RUBY-168 + # @expected_result broadcast_address and listen_address should be retrieved + # + # @test_category host:metadata + # + def test_host_broadcast_listen_address + cluster = Cassandra.cluster + cluster.hosts.each do |host| + assert_equal '127.0.0.1', host.broadcast_address.to_s + assert_equal '127.0.0.1', host.listen_address.to_s + end + ensure + cluster && cluster.close + end + end From bcf6f7b4d5c872fa0ad013ecbf9df526e3a95d2d Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Wed, 30 Mar 2016 12:31:31 -0700 Subject: [PATCH 020/196] RUBY-165 -- Added input validation around protocol_version cluster option. --- lib/cassandra.rb | 10 ++++++++++ spec/cassandra_spec.rb | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/lib/cassandra.rb b/lib/cassandra.rb index 0e31a4387..02d73728e 100644 --- a/lib/cassandra.rb +++ b/lib/cassandra.rb @@ -633,6 +633,16 @@ def self.validate_and_massage_options(options) end end + if options.key?(:protocol_version) + protocol_version = options[:protocol_version] + unless protocol_version.nil? + Util.assert_instance_of(::Integer, protocol_version) + Util.assert_one_of(1..4, protocol_version) do + ":protocol_version must be a positive integer, #{protocol_version.inspect} given" + end + end + end + if options.key?(:futures_factory) futures_factory = options[:futures_factory] methods = [:error, :value, :promise, :all] diff --git a/spec/cassandra_spec.rb b/spec/cassandra_spec.rb index bcd5ad1a2..5b91e28fb 100644 --- a/spec/cassandra_spec.rb +++ b/spec/cassandra_spec.rb @@ -317,6 +317,16 @@ def unavailable expect(C.validate(page_size: nil)).to eq({ page_size: nil }) end + it 'should validate :protocol_version' do + expect { C.validate(protocol_version: 'a') }.to raise_error(ArgumentError) + expect { C.validate(protocol_version: 0) }.to raise_error(ArgumentError) + expect { C.validate(protocol_version: 1.5) }.to raise_error(ArgumentError) + expect { C.validate(protocol_version: 5) }.to raise_error(ArgumentError) + expect(C.validate(protocol_version: 1)).to eq({ protocol_version: 1 }) + expect(C.validate(protocol_version: 4)).to eq({ protocol_version: 4 }) + expect(C.validate(protocol_version: nil)).to eq({ protocol_version: nil }) + end + it 'should validate :futures_factory option' do class GoodFactory def error From cfeca765887b42ae8d779aaf96a62854d76c5674 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Wed, 30 Mar 2016 12:35:17 -0700 Subject: [PATCH 021/196] RUBY-165 -- Added input validation around protocol_version cluster option. * Disable rubocop warnings around validate_and_massage_options method. --- lib/cassandra.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/cassandra.rb b/lib/cassandra.rb index 02d73728e..edf94a68c 100644 --- a/lib/cassandra.rb +++ b/lib/cassandra.rb @@ -323,6 +323,9 @@ def self.cluster_async(options = {}) # @private SSL_CLASSES = [::TrueClass, ::FalseClass, ::OpenSSL::SSL::SSLContext].freeze + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/CyclomaticComplexity + # rubocop:disable Metrics/PerceivedComplexity # @private def self.validate_and_massage_options(options) options = options.select do |key, _| From d49e0df46bc98935e18162102d94b7c3f2e8d187 Mon Sep 17 00:00:00 2001 From: Kishan Karunaratne Date: Wed, 30 Mar 2016 14:28:37 -0700 Subject: [PATCH 022/196] [RUBY-165] Integration tests for adding protocol_version as Cluster option --- integration/session_test.rb | 58 +++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/integration/session_test.rb b/integration/session_test.rb index eeab8e0b2..895dc04a5 100644 --- a/integration/session_test.rb +++ b/integration/session_test.rb @@ -772,4 +772,62 @@ def test_host_broadcast_listen_address cluster && cluster.close end + # Test for setting the protocol_version + # + # test_can_set_protocol_version_explicitly tests that the protocol_version for a cluster object can be set, such that + # this version is used explicitly when connecting to the Cassandra cluster, rather than automatically negotiating it. + # It first tests that a valid protocol version can be set. It then tests that setting a protocol version lower than + # the Cassandra cluster supports throws a Cassandra::Errors::NoHostsAvailable, or ArgumentError if invalid. Finally, + # it tests that setting a protocol version higher than the Cassandra cluster supports throws a + # Cassandra::Errors::ProtocolError, or ArgumentError if invalid. + # + # @expected_errors [ArgumentError] When an invalid protocol version (< 1 or > 4) is specified. + # @expected_errors [Cassandra::Errors::NoHostsAvailable] When a protocol version lower than supported is specified. + # @expected_errors [Cassandra::Errors::ProtocolError] When a protocol version higher than supported is specified. + # + # @since 3.0.0 + # @jira_ticket RUBY-165 + # @expected_result protocol versions specified should be honored when connecting to the Cassandra cluster. + # + # @test_category connection + # + def test_can_set_protocol_version_explicitly + ## Setting a valid protocol version + if CCM.cassandra_version < '3.0.0' + cluster = Cassandra.cluster(protocol_version: 1) + assert_equal 1, cluster.instance_variable_get(:@connection_options).protocol_version + else + cluster = Cassandra.cluster(protocol_version: 3) + assert_equal 3, cluster.instance_variable_get(:@connection_options).protocol_version + end + + ## Setting protocol version lower than supported + # Protocol version 0 is invalid + assert_raises(ArgumentError) do + Cassandra.cluster(protocol_version: 0) + end + + # C* 3.0+ does not support protocol version < 3 + if CCM.cassandra_version >= '3.0.0' + assert_raises(Cassandra::Errors::NoHostsAvailable) do + Cassandra.cluster(protocol_version: 2) + end + end + + ## Setting protocol version higher than supported + # Protocol version > 4 is invalid + assert_raises(ArgumentError) do + Cassandra.cluster(protocol_version: 10) + end + + # C* < 2.2 does not support protocol version 4 + if CCM.cassandra_version < '2.2.0' + assert_raises(Cassandra::Errors::ProtocolError) do + Cassandra.cluster(protocol_version: 4) + end + end + ensure + cluster && cluster.close + end + end From 40db1efd2a35872c59b1d0e9765b85f4d519a569 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Wed, 30 Mar 2016 16:23:30 -0700 Subject: [PATCH 023/196] RUBY-165 -- Added input validation around protocol_version cluster option. * Updated README to mention that specifying v1 or v2 and contacting a 3.0-3.4 node results in a NoHostsAvailable error rather than ProtocolError. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 3f79e919a..f41cfe097 100644 --- a/README.md +++ b/README.md @@ -194,6 +194,8 @@ the release. ## Known bugs & limitations +* Specifying a `protocol_version` option of 1 or 2 in cluster options will fail with a + `NoHostsAvailable` error rather than a `ProtocolError` against Cassandra node versions 3.0-3.4. * JRuby 1.6 is not officially supported, although 1.6.8 should work. * Because the driver reactor is using `IO.select`, the maximum number of tcp connections allowed is 1024. * Because the driver uses `IO#write_nonblock`, Windows is not supported. From 17da82f5b03294db2c8581e61660a6dc91ea75dd Mon Sep 17 00:00:00 2001 From: Kishan Karunaratne Date: Fri, 1 Apr 2016 14:02:09 -0700 Subject: [PATCH 024/196] [RUBY-178] Integration tests and cucumber features for indexes --- features/basics/schema_metadata.feature | 143 ++++++++++---- .../functions/user_defined_aggregate_test.rb | 1 - .../functions/user_defined_function_test.rb | 1 - integration/indexes/indexes_test.rb | 178 ++++++++++++++++++ integration/integration_test_case.rb | 1 + .../{functions => }/schema_change_listener.rb | 6 + 6 files changed, 288 insertions(+), 42 deletions(-) create mode 100644 integration/indexes/indexes_test.rb rename integration/{functions => }/schema_change_listener.rb (90%) diff --git a/features/basics/schema_metadata.feature b/features/basics/schema_metadata.feature index dabd45d66..eb1502b5f 100644 --- a/features/basics/schema_metadata.feature +++ b/features/basics/schema_metadata.feature @@ -46,13 +46,12 @@ Feature: Schema Metadata ) """ - @cassandra-version-specific @cassandra-version-less-3.0 Scenario: Getting index metadata Given the following schema: """cql CREATE KEYSPACE simplex WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 3}; - CREATE TABLE simplex.test_table (f1 int primary key, f2 int); - CREATE INDEX ind1 ON simplex.test_table (f2); + CREATE TABLE simplex.test_table (a int primary key, b int); + CREATE INDEX ind1 ON simplex.test_table (b); """ And the following example: """ruby @@ -60,23 +59,33 @@ Feature: Schema Metadata cluster = Cassandra.cluster - cluster.keyspace('simplex').table('test_table').each_index do |index| - puts index.to_cql - end + index = cluster.keyspace('simplex').table('test_table').index('ind1') + puts index.to_cql + + puts "" + puts "Name: #{index.name}" + puts "Table name: #{index.table.name}" + puts "Kind: #{index.kind}" + puts "Target: #{index.target}" """ When it is executed Then its output should contain: """cql - CREATE INDEX "ind1" ON simplex.test_table ("f2"); + CREATE INDEX "ind1" ON simplex.test_table (b); + + Name: ind1 + Table name: test_table + Kind: composites + Target: b """ @cassandra-version-specific @cassandra-version-3.0 - Scenario: Getting index metadata on 3.0 + Scenario: Getting index metadata on full collections Given the following schema: """cql CREATE KEYSPACE simplex WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 3}; - CREATE TABLE simplex.test_table (f1 int primary key, f2 int); - CREATE INDEX ind1 ON simplex.test_table (f2); + CREATE TABLE simplex.test_table (a int PRIMARY KEY, b frozen>); + CREATE INDEX ind1 ON simplex.test_table (full(b)); """ And the following example: """ruby @@ -84,25 +93,34 @@ Feature: Schema Metadata cluster = Cassandra.cluster - cluster.keyspace('simplex').table('test_table').each_index do |index| - puts index.to_cql - end + index = cluster.keyspace('simplex').table('test_table').index('ind1') + puts index.to_cql + + puts "" + puts "Name: #{index.name}" + puts "Table name: #{index.table.name}" + puts "Kind: #{index.kind}" + puts "Target: #{index.target}" """ When it is executed Then its output should contain: """cql - CREATE INDEX "ind1" ON simplex.test_table (f2); + CREATE INDEX "ind1" ON simplex.test_table (full(b)); + + Name: ind1 + Table name: test_table + Kind: composites + Target: full(b) """ @cassandra-version-specific @cassandra-version-3.0 - Scenario: Getting materialized view metadata + Scenario: Getting multiple index metadata on same column Given the following schema: """cql CREATE KEYSPACE simplex WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 3}; - CREATE TABLE simplex.test_table (f1 int PRIMARY KEY, f2 int, f3 int) ; - CREATE MATERIALIZED VIEW simplex.test_view AS - SELECT f1, f2 FROM simplex.test_table WHERE f2 IS NOT NULL - PRIMARY KEY (f1, f2); + CREATE TABLE simplex.test_table (a int PRIMARY KEY, b map); + CREATE INDEX ind1 ON simplex.test_table (keys(b)); + CREATE INDEX ind2 ON simplex.test_table (values(b)); """ And the following example: """ruby @@ -110,31 +128,33 @@ Feature: Schema Metadata cluster = Cassandra.cluster - view = cluster.keyspace('simplex').materialized_view('test_view') - puts view.to_cql + index = cluster.keyspace('simplex').table('test_table').index('ind1') + puts index.to_cql + + puts "" + puts "Name: #{index.name}" + puts "Target: #{index.target}" + + puts "" + index = cluster.keyspace('simplex').table('test_table').index('ind2') + puts index.to_cql + + puts "" + puts "Name: #{index.name}" + puts "Target: #{index.target}" """ When it is executed Then its output should contain: """cql - CREATE MATERIALIZED VIEW simplex.test_view AS - SELECT "f1", "f2" - FROM simplex.test_table - WHERE f2 IS NOT NULL - PRIMARY KEY (("f1"), "f2") - WITH bloom_filter_fp_chance = 0.01 - AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} - AND comment = '' - AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} - AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} - AND crc_check_chance = 1.0 - AND dclocal_read_repair_chance = 0.1 - AND default_time_to_live = 0 - AND gc_grace_seconds = 864000 - AND max_index_interval = 2048 - AND memtable_flush_period_in_ms = 0 - AND min_index_interval = 128 - AND read_repair_chance = 0.0 - AND speculative_retry = '99PERCENTILE'; + CREATE INDEX "ind1" ON simplex.test_table (keys(b)); + + Name: ind1 + Target: keys(b) + + CREATE INDEX "ind2" ON simplex.test_table (values(b)); + + Name: ind2 + Target: values(b) """ @cassandra-version-specific @cassandra-version-2.1 @@ -276,3 +296,46 @@ Feature: Schema Metadata State function: avgstate Final function: avgfinal """ + + @cassandra-version-specific @cassandra-version-3.0 + Scenario: Getting materialized view metadata + Given the following schema: + """cql + CREATE KEYSPACE simplex WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 3}; + CREATE TABLE simplex.test_table (f1 int PRIMARY KEY, f2 int, f3 int) ; + CREATE MATERIALIZED VIEW simplex.test_view AS + SELECT f1, f2 FROM simplex.test_table WHERE f2 IS NOT NULL + PRIMARY KEY (f1, f2); + """ + And the following example: + """ruby + require 'cassandra' + + cluster = Cassandra.cluster + + view = cluster.keyspace('simplex').materialized_view('test_view') + puts view.to_cql + """ + When it is executed + Then its output should contain: + """cql + CREATE MATERIALIZED VIEW simplex.test_view AS + SELECT "f1", "f2" + FROM simplex.test_table + WHERE f2 IS NOT NULL + PRIMARY KEY (("f1"), "f2") + WITH bloom_filter_fp_chance = 0.01 + AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} + AND comment = '' + AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} + AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} + AND crc_check_chance = 1.0 + AND dclocal_read_repair_chance = 0.1 + AND default_time_to_live = 0 + AND gc_grace_seconds = 864000 + AND max_index_interval = 2048 + AND memtable_flush_period_in_ms = 0 + AND min_index_interval = 128 + AND read_repair_chance = 0.0 + AND speculative_retry = '99PERCENTILE'; + """ diff --git a/integration/functions/user_defined_aggregate_test.rb b/integration/functions/user_defined_aggregate_test.rb index 1fe63ace5..88a75b0e4 100644 --- a/integration/functions/user_defined_aggregate_test.rb +++ b/integration/functions/user_defined_aggregate_test.rb @@ -17,7 +17,6 @@ #++ require File.dirname(__FILE__) + '/../integration_test_case.rb' -require_relative 'schema_change_listener' # noinspection RubyInstanceMethodNamingConvention class UserDefinedAggregateTest < IntegrationTestCase diff --git a/integration/functions/user_defined_function_test.rb b/integration/functions/user_defined_function_test.rb index 84ec47131..6010d7a2b 100644 --- a/integration/functions/user_defined_function_test.rb +++ b/integration/functions/user_defined_function_test.rb @@ -17,7 +17,6 @@ #++ require File.dirname(__FILE__) + '/../integration_test_case.rb' -require_relative 'schema_change_listener' # noinspection RubyInstanceMethodNamingConvention class UserDefinedFunctionTest < IntegrationTestCase diff --git a/integration/indexes/indexes_test.rb b/integration/indexes/indexes_test.rb new file mode 100644 index 000000000..3b961562c --- /dev/null +++ b/integration/indexes/indexes_test.rb @@ -0,0 +1,178 @@ +# encoding: utf-8 + +#-- +# Copyright 2013-2016 DataStax, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + +require File.dirname(__FILE__) + '/../integration_test_case.rb' + +class IndexesTest < IntegrationTestCase + + def setup + @@ccm_cluster.setup_schema("CREATE KEYSPACE simplex WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}") + + @cluster = Cassandra.cluster( + schema_refresh_delay: 0.1, + schema_refresh_timeout: 0.1 + ) + @listener = SchemaChangeListener.new(@cluster) + @session = @cluster.connect('simplex') + end + + def teardown + @cluster && @cluster.close + end + + # Test for creating indexes + # + # test_can_create_index tests that indexes can be created using the driver. It first creates a simple table and a + # simplex index on that table. It then verifies that the index metadata is being properly retrieved. + # + # @since 3.0.0 + # @jira_ticket RUBY-178 + # @expected_result index should be created and its metadata should be retrieved. + # + # @test_category indexes + # + def test_can_create_index + @session.execute("CREATE TABLE simplex.test (a text PRIMARY KEY, b text)") + @session.execute("CREATE INDEX b_index ON simplex.test (b)") + + @listener.wait_for_index('simplex', 'test', 'b_index') + + assert @cluster.keyspace('simplex').table('test').has_index?('b_index') + index = @cluster.keyspace('simplex').table('test').index('b_index') + assert_equal 'b_index', index.name + assert_equal 'test', index.table.name + assert_equal :composites, index.kind + assert_equal 'b', index.target + end + + # Test for creating indexes on partial collections + # + # test_can_create_index_on_partial_collections tests that indexes can be created on partial (non-frozen) collections. + # The indexes can be created on either the keys or the values, but not both at the same time. It first creates a table + # and an index on the keys of the collection, verifying the metadata once created. It then drops the original index and + # creates another index, but this time on the values of the collection. + # + # @since 3.0.0 + # @jira_ticket RUBY-178 + # @expected_result partial collection indexes should be created and their metadata should be retrieved. + # + # @test_category indexes + # + def test_can_create_index_on_partial_collections + skip("Secondary index on partial collections were introduced in Cassandra 2.1") if CCM.cassandra_version < '2.1.0' + + @session.execute("CREATE TABLE simplex.collection_test (a int PRIMARY KEY, b map)") + @session.execute("CREATE INDEX b_index ON simplex.collection_test (keys(b))") + + @listener.wait_for_index('simplex', 'collection_test', 'b_index') + + assert @cluster.keyspace('simplex').table('collection_test').has_index?('b_index') + index = @cluster.keyspace('simplex').table('collection_test').index('b_index') + assert_equal 'b_index', index.name + assert_equal 'collection_test', index.table.name + assert_equal :composites, index.kind + assert_equal 'keys(b)', index.target + + @session.execute("DROP INDEX b_index") + @session.execute("CREATE INDEX b_index ON simplex.collection_test (b)") + + @listener.wait_for_index('simplex', 'collection_test', 'b_index') + + assert @cluster.keyspace('simplex').table('collection_test').has_index?('b_index') + index = @cluster.keyspace('simplex').table('collection_test').index('b_index') + assert_equal 'b_index', index.name + assert_equal 'collection_test', index.table.name + assert_equal :composites, index.kind + if CCM.cassandra_version < '3.0.0' + assert_equal 'b', index.target + else + assert_equal 'values(b)', index.target + end + end + + # Test for creating indexes on full collections + # + # test_can_create_index_on_full_collections tests that indexes can be created on full (frozen) collections. It first + # creates a table which includes a frozen collection. It then creates a full index on the collection, verifying that + # the metadata is properly retrieved. + # + # @since 3.0.0 + # @jira_ticket RUBY-178 + # @expected_result full collection indexes should be created and their metadata should be retrieved. + # + # @test_category indexes + # + def test_can_create_index_on_full_collections + skip("Secondary index on full collections were introduced in Cassandra 2.1.3") if CCM.cassandra_version.to_i < '2.1.3'.to_i + + @session.execute("CREATE TABLE simplex.collection_test (a int PRIMARY KEY, b frozen>)") + @session.execute("CREATE INDEX b_index ON simplex.collection_test (full(b))") + + @listener.wait_for_index('simplex', 'collection_test', 'b_index') + + assert @cluster.keyspace('simplex').table('collection_test').has_index?('b_index') + index = @cluster.keyspace('simplex').table('collection_test').index('b_index') + assert_equal 'b_index', index.name + assert_equal 'collection_test', index.table.name + assert_equal :composites, index.kind + if CCM.cassandra_version < '3.0.0' + assert_equal 'b', index.target + else + assert_equal 'full(b)', index.target + end + end + + # Test for creating multiple indexes on the same column + # + # test_can_create_multiple_indexes_same_column tests that multiple indexes can be created on the same column. It first + # creates a table which includes a non-frozen collection. It then creates two indexes: one for the key and another for + # the value of the collection. It then verifies the metadata associated with each index. + # + # @since 3.0.0 + # @jira_ticket RUBY-178 + # @expected_result multiple indexes should be created and their metadata should be retrieved. + # + # @test_category indexes + # + def test_can_create_multiple_indexes_same_column + skip("Multiple indexes on same column were introduced in Cassandra 3.0.0") if CCM.cassandra_version < '3.0.0' + + @session.execute("CREATE TABLE simplex.multi_index_test (a int PRIMARY KEY, b map)") + @session.execute("CREATE INDEX key_index ON simplex.multi_index_test (keys(b))") + @session.execute("CREATE INDEX value_index ON simplex.multi_index_test (values(b))") + + @listener.wait_for_index('simplex', 'multi_index_test', 'key_index') + @listener.wait_for_index('simplex', 'multi_index_test', 'value_index') + + assert @cluster.keyspace('simplex').table('multi_index_test').has_index?('key_index') + assert @cluster.keyspace('simplex').table('multi_index_test').has_index?('value_index') + + key_index = @cluster.keyspace('simplex').table('multi_index_test').index('key_index') + assert_equal 'key_index', key_index.name + assert_equal 'multi_index_test', key_index.table.name + assert_equal :composites, key_index.kind + assert_equal 'keys(b)', key_index.target + + value_index = @cluster.keyspace('simplex').table('multi_index_test').index('value_index') + assert_equal 'value_index', value_index.name + assert_equal 'multi_index_test', value_index.table.name + assert_equal :composites, value_index.kind + assert_equal 'values(b)', value_index.target + end + +end diff --git a/integration/integration_test_case.rb b/integration/integration_test_case.rb index b8b1737b3..28a0d0bc9 100644 --- a/integration/integration_test_case.rb +++ b/integration/integration_test_case.rb @@ -18,6 +18,7 @@ require File.dirname(__FILE__) + '/../support/ccm.rb' require File.dirname(__FILE__) + '/../support/retry.rb' +require File.dirname(__FILE__) + '/schema_change_listener.rb' require 'minitest/unit' require 'minitest/autorun' require 'cassandra' diff --git a/integration/functions/schema_change_listener.rb b/integration/schema_change_listener.rb similarity index 90% rename from integration/functions/schema_change_listener.rb rename to integration/schema_change_listener.rb index f03d5a925..1e5b9899f 100644 --- a/integration/functions/schema_change_listener.rb +++ b/integration/schema_change_listener.rb @@ -54,6 +54,12 @@ def wait_for_aggregate(keyspace_name, aggregate_name, *args) end end + def wait_for_index(keyspace_name, table_name, index_name, *args) + wait_for_change(keyspace_name, 2) do |ks| + ks.table(table_name).has_index?(index_name, *args) + end + end + def keyspace_changed(keyspace) # This looks a little strange, but here's the idea: if we don't have # Condition's for this keyspace, immediately return. Otherwise, for each From 3ae09ba656dde6109b03a2ba29c94e53076a93f9 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Fri, 1 Apr 2016 14:44:54 -0700 Subject: [PATCH 025/196] Fixed typo in comment --- integration/indexes/indexes_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration/indexes/indexes_test.rb b/integration/indexes/indexes_test.rb index 3b961562c..fb1316307 100644 --- a/integration/indexes/indexes_test.rb +++ b/integration/indexes/indexes_test.rb @@ -38,7 +38,7 @@ def teardown # Test for creating indexes # # test_can_create_index tests that indexes can be created using the driver. It first creates a simple table and a - # simplex index on that table. It then verifies that the index metadata is being properly retrieved. + # simple index on that table. It then verifies that the index metadata is being properly retrieved. # # @since 3.0.0 # @jira_ticket RUBY-178 From d296db3fb14eb533a544d749959cae9f605a5ba1 Mon Sep 17 00:00:00 2001 From: Kishan Karunaratne Date: Fri, 1 Apr 2016 17:16:10 -0700 Subject: [PATCH 026/196] [RUBY-181] Integration tests and cucumber features for table metadata --- features/basics/schema_metadata.feature | 53 +++++---- integration/metadata_test.rb | 150 ++++++++++++++++++++++++ integration/schema_change_listener.rb | 6 + 3 files changed, 183 insertions(+), 26 deletions(-) create mode 100644 integration/metadata_test.rb diff --git a/features/basics/schema_metadata.feature b/features/basics/schema_metadata.feature index eb1502b5f..cc1daa685 100644 --- a/features/basics/schema_metadata.feature +++ b/features/basics/schema_metadata.feature @@ -5,44 +5,45 @@ Feature: Schema Metadata Background: Given a running cassandra cluster - @cassandra-version-specific @cassandra-version-less-3.0 Scenario: Getting table metadata - Given the following example: - """ruby - require 'cassandra' - - cluster = Cassandra.cluster - - puts cluster.keyspace('system').table("IndexInfo").to_cql - """ - When it is executed - Then its output should contain: + Given the following schema: """cql - CREATE TABLE system."IndexInfo" ( - table_name text, - index_name text, - PRIMARY KEY (table_name, index_name) - ) + CREATE KEYSPACE simplex WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 3}; + CREATE TABLE simplex.users (user_id bigint, first text, last text, age int, PRIMARY KEY (user_id, last)) """ - - @cassandra-version-specific @cassandra-version-3.0 - Scenario: Getting table metadata on 3.0 - Given the following example: + And the following example: """ruby require 'cassandra' cluster = Cassandra.cluster - puts cluster.keyspace('system').table("IndexInfo").to_cql + table_meta = cluster.keyspace('simplex').table('users') + puts "Name: #{table_meta.name}" + puts "Keyspace: #{table_meta.keyspace.name}" + puts "Partition key: #{table_meta.partition_key[0].name}" + puts "Clustering column: #{table_meta.clustering_columns[0].name}" + puts "Clustering order: #{table_meta.clustering_order.first}" + puts "Num columns: #{table_meta.columns.size}" + + puts "" + puts table_meta.to_cql """ When it is executed Then its output should contain: """cql - CREATE TABLE system."IndexInfo" ( - table_name text, - index_name text, - value 'org.apache.cassandra.db.marshal.EmptyType', - PRIMARY KEY (table_name, index_name) + Name: users + Keyspace: simplex + Partition key: user_id + Clustering column: last + Clustering order: asc + Num columns: 4 + + CREATE TABLE simplex.users ( + user_id bigint, + last text, + age int, + first text, + PRIMARY KEY (user_id, last) ) """ diff --git a/integration/metadata_test.rb b/integration/metadata_test.rb new file mode 100644 index 000000000..ff490d3bb --- /dev/null +++ b/integration/metadata_test.rb @@ -0,0 +1,150 @@ +# encoding: utf-8 + +#-- +# Copyright 2013-2016 DataStax, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + +require File.dirname(__FILE__) + '/integration_test_case.rb' + +class MetadataTest < IntegrationTestCase + + def setup + @@ccm_cluster.setup_schema("CREATE KEYSPACE simplex WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}") + + @cluster = Cassandra.cluster( + schema_refresh_delay: 0.1, + schema_refresh_timeout: 0.1 + ) + @listener = SchemaChangeListener.new(@cluster) + @session = @cluster.connect('simplex') + @session.execute("CREATE TABLE simplex.users (user_id bigint, first text, last text, age int, PRIMARY KEY (user_id, last))") + @listener.wait_for_table('simplex', 'users') + end + + def teardown + @cluster && @cluster.close + end + + # Test for retrieving table metadata + # + # test_can_retrieve_table_metadata tests that all pieces of table metadata can be retrieved. It goes through each piece + # of table metadata and verifies that each piece is as expected. + # + # @since 3.0.0 + # @jira_ticket RUBY-181 + # @expected_result table metadata should be retrieved. + # + # @test_category metadata + # + def test_can_retrieve_table_metadata + assert @cluster.keyspace('simplex').has_table?('users') + table_meta = @cluster.keyspace('simplex').table('users') + assert_equal 'users', table_meta.name + assert_equal 'simplex', table_meta.keyspace.name + assert_empty table_meta.indexes + refute_nil table_meta.id unless CCM.cassandra_version < '3.0.0' + refute_nil table_meta.options + + assert_equal 2, table_meta.primary_key.size + assert_equal 'user_id', table_meta.primary_key[0].name + assert_equal :bigint, table_meta.primary_key[0].type.kind + assert_equal 'last', table_meta.primary_key[1].name + assert_equal :text, table_meta.primary_key[1].type.kind + + assert_equal 1, table_meta.partition_key.size + assert_equal 'user_id', table_meta.partition_key[0].name + assert_equal :bigint, table_meta.partition_key[0].type.kind + assert_equal 1, table_meta.clustering_columns.size + assert_equal 'last', table_meta.clustering_columns[0].name + assert_equal :text, table_meta.clustering_columns[0].type.kind + assert_equal :asc, table_meta.clustering_order.first + + assert_equal 4, table_meta.columns.size + table_meta.each_column do |column| + assert ['user_id', 'first', 'last', 'age'].any? { |name| name == column.name } + assert [:bigint, :text, :int].any? { |type| type == column.type.kind } + end + end + + # Test for column ordering in table metadata + # + # test_column_ordering_is_deterministic tests that the metadata relating to the columns are retrieved in the proper + # order. This proper order is: partition key, clustering columns, and then all other columns alphanumerically. + # + # @since 3.0.0 + # @jira_ticket RUBY-180 + # @expected_result column ordering should be correct in table metadata. + # + # @test_category metadata + # + def test_column_ordering_is_deterministic + assert @cluster.keyspace('simplex').has_table?('users') + table_meta = @cluster.keyspace('simplex').table('users') + table_cql = Regexp.new(/CREATE TABLE simplex\.users \( + user_id bigint, + last text, + age int, + first text, + PRIMARY KEY \(user_id, last\) +\)/) + + assert_equal 0, table_meta.to_cql =~ table_cql + + col_names = ['user_id', 'last', 'age', 'first'] + table_meta.each_column do |column| + assert_equal col_names[0], column.name + col_names.delete_at(0) + end + end + + # Test for retrieving crc_check_balance property + # + # test_table_metadata_contains_crc_check_balance tests that the 'crc_check_balance' property of table metadata is able + # to be retrieved. + # + # @since 3.0.0 + # @jira_ticket RUBY-179 + # @expected_result crc_check_balance property should be retrieved from the table metadata + # + # @test_category metadata + # + def test_table_metadata_contains_crc_check_balance + skip("The crc_check_balance property on a table was introduced in Cassandra 3.0") if CCM.cassandra_version < '3.0.0' + + assert @cluster.keyspace('simplex').has_table?('users') + table_meta = @cluster.keyspace('simplex').table('users') + assert_equal 1.0, table_meta.options.crc_check_chance + end + + # Test for retrieving extensions property + # + # test_table_metadata_contains_extensions tests that the 'extensions' property of table metadata is able to be + # retrieved. + # + # @since 3.0.0 + # @jira_ticket RUBY-170 + # @expected_result extensions property should be retrieved from the table metadata + # + # @test_category metadata + # + def test_table_metadata_contains_extensions + skip("The extensions property on a table was introduced in Cassandra 3.0") if CCM.cassandra_version < '3.0.0' + + assert @cluster.keyspace('simplex').has_table?('users') + table_meta = @cluster.keyspace('simplex').table('users') + assert_empty table_meta.options.extensions + end + +end diff --git a/integration/schema_change_listener.rb b/integration/schema_change_listener.rb index 1e5b9899f..6f8958a6f 100644 --- a/integration/schema_change_listener.rb +++ b/integration/schema_change_listener.rb @@ -54,6 +54,12 @@ def wait_for_aggregate(keyspace_name, aggregate_name, *args) end end + def wait_for_table(keyspace_name, table_name, *args) + wait_for_change(keyspace_name, 2) do |ks| + ks.has_table?(table_name, *args) + end + end + def wait_for_index(keyspace_name, table_name, index_name, *args) wait_for_change(keyspace_name, 2) do |ks| ks.table(table_name).has_index?(index_name, *args) From 8bfbef8d70e420abbfb781ea4ec991f68f7ec482 Mon Sep 17 00:00:00 2001 From: Kishan Karunaratne Date: Fri, 1 Apr 2016 17:16:59 -0700 Subject: [PATCH 027/196] Integration tests and cucumber features for keyspace metadata --- features/basics/schema_metadata.feature | 33 +++++++++++++++++++++++++ integration/metadata_test.rb | 26 +++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/features/basics/schema_metadata.feature b/features/basics/schema_metadata.feature index cc1daa685..011fb1bcc 100644 --- a/features/basics/schema_metadata.feature +++ b/features/basics/schema_metadata.feature @@ -5,6 +5,39 @@ Feature: Schema Metadata Background: Given a running cassandra cluster + Scenario: Getting keyspace metadata + Given the following schema: + """cql + CREATE KEYSPACE simplex WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 3}; + """ + And the following example: + """ruby + require 'cassandra' + + cluster = Cassandra.cluster + + ks_meta = cluster.keyspace('simplex') + puts "Name: #{ks_meta.name}" + puts "Replication class: #{ks_meta.replication.klass}" + puts "Replication factor: #{ks_meta.replication.options['replication_factor'].to_i}" + puts "Durable writes?: #{ks_meta.durable_writes?}" + puts "# tables: #{ks_meta.tables.size}" + + puts "" + puts ks_meta.to_cql + """ + When it is executed + Then its output should contain: + """cql + Name: simplex + Replication class: SimpleStrategy + Replication factor: 3 + Durable writes?: true + # tables: 0 + + CREATE KEYSPACE simplex WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '3'} AND durable_writes = true; + """ + Scenario: Getting table metadata Given the following schema: """cql diff --git a/integration/metadata_test.rb b/integration/metadata_test.rb index ff490d3bb..1e7a65928 100644 --- a/integration/metadata_test.rb +++ b/integration/metadata_test.rb @@ -37,6 +37,32 @@ def teardown @cluster && @cluster.close end + # Test for retrieving keyspace metadata + # + # test_can_retrieve_keyspace_metadata tests that all pieces of keyspace metadata can be retrieved. It goes through + # each piece of keyspace metadata and verifies that each piece is as expected. + # + # @since 3.0.0 + # @jira_ticket RUBY-181 + # @expected_result keyspace metadata should be retrieved. + # + # @test_category metadata + # + def test_can_retrieve_keyspace_metadata + ks_meta = @cluster.keyspace('simplex') + assert_equal 'simplex', ks_meta.name + assert_equal 'SimpleStrategy', ks_meta.replication.klass + assert_equal 1, ks_meta.replication.options['replication_factor'].to_i + assert ks_meta.durable_writes? + assert ks_meta.has_table?('users') + assert_equal 1, ks_meta.tables.size + + ks_cql = Regexp.new(/CREATE KEYSPACE simplex WITH replication = {'class': 'SimpleStrategy', \ +'replication_factor': '1'} AND durable_writes = true;/) + + assert_match ks_cql, ks_meta.to_cql + end + # Test for retrieving table metadata # # test_can_retrieve_table_metadata tests that all pieces of table metadata can be retrieved. It goes through each piece From 49864f6c6e7e8c52b45790d94d9333812722ca58 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Tue, 5 Apr 2016 15:03:50 -0700 Subject: [PATCH 028/196] RUBY-185 - Added logic in fetcher to handle static-compact tables in 3.0. * Added integration test cases. --- integration/metadata_test.rb | 72 +++++++++++++++++++++++- lib/cassandra/cluster/schema/fetchers.rb | 16 +++++- 2 files changed, 86 insertions(+), 2 deletions(-) diff --git a/integration/metadata_test.rb b/integration/metadata_test.rb index 1e7a65928..b56b93b00 100644 --- a/integration/metadata_test.rb +++ b/integration/metadata_test.rb @@ -31,6 +31,10 @@ def setup @session = @cluster.connect('simplex') @session.execute("CREATE TABLE simplex.users (user_id bigint, first text, last text, age int, PRIMARY KEY (user_id, last))") @listener.wait_for_table('simplex', 'users') + @session.execute("CREATE TABLE simplex.blobby (key blob PRIMARY KEY, f1 blob, f2 blob) WITH COMPACT STORAGE") + @listener.wait_for_table('simplex', 'blobby') + @session.execute("CREATE TABLE simplex.dense (f1 int, f2 int, f3 int, PRIMARY KEY (f1, f2)) WITH COMPACT STORAGE") + @listener.wait_for_table('simplex', 'dense') end def teardown @@ -55,7 +59,7 @@ def test_can_retrieve_keyspace_metadata assert_equal 1, ks_meta.replication.options['replication_factor'].to_i assert ks_meta.durable_writes? assert ks_meta.has_table?('users') - assert_equal 1, ks_meta.tables.size + assert_equal 3, ks_meta.tables.size ks_cql = Regexp.new(/CREATE KEYSPACE simplex WITH replication = {'class': 'SimpleStrategy', \ 'replication_factor': '1'} AND durable_writes = true;/) @@ -135,6 +139,72 @@ def test_column_ordering_is_deterministic end end + # Test for skipping internal columns in static-compact tables + # + # test_skip_internal_columns_for_static_compact_table tests that the "column1 text" clustering + # column and "value blob" regular columns are excluded from table metadata for static-compact tables. + # It also coerces columns marked static to be regular instead. + # + # @jira_ticket RUBY-185 + # @expected_result the metadata should only report columns we've consciously added to the table. + # + # @test_category metadata + # + def test_skip_internal_columns_for_static_compact_table + assert @cluster.keyspace('simplex').has_table?('blobby') + table_meta = @cluster.keyspace('simplex').table('blobby') + table_cql = Regexp.new(/CREATE TABLE simplex\.blobby \( + key blob PRIMARY KEY, + f1 blob, + f2 blob +\)/) + + assert_equal 0, table_meta.to_cql =~ table_cql, "actual cql: #{table_meta.to_cql}" + assert_equal 3, table_meta.columns.size + + table_meta.each_column do |column| + assert ['key', 'f1', 'f2'].any? { |name| name == column.name } + assert_equal :blob, column.type.kind + refute column.static? + end + end + + # Test for skipping internal columns in dense tables + # + # test_skip_internal_columns_for_dense_table tests that "value " column is excluded from table metadata + # for dense tables. + # + # @jira_ticket RUBY-185 + # @expected_result the metadata should only report columns we've consciously added to the table. + # + # @test_category metadata + # + def test_skip_internal_columns_for_dense_table + # NOTE: It seems that the dense table does not have an empty-type column. The Java driver has logic to + # handle that, but maybe it's outdated and unnecessary. This test serves the purpose of keeping us on guard, + # in case some version of C* does create the internal column; if we encounter such a C*, the test will fail and + # we'll go to the effort of fixing the issue. + + assert @cluster.keyspace('simplex').has_table?('dense') + table_meta = @cluster.keyspace('simplex').table('dense') + table_cql = Regexp.new(/CREATE TABLE simplex\.dense \( + f1 int, + f2 int, + f3 int, + PRIMARY KEY \(f1, f2\) +\) +WITH COMPACT STORAGE/) + + assert_equal 0, table_meta.to_cql =~ table_cql, "actual cql: #{table_meta.to_cql}" + assert_equal 3, table_meta.columns.size + + table_meta.each_column do |column| + assert ['f1', 'f2', 'f3'].any? { |name| name == column.name } + assert_equal :int, column.type.kind + refute column.static? + end + end + # Test for retrieving crc_check_balance property # # test_table_metadata_contains_crc_check_balance tests that the 'crc_check_balance' property of table metadata is able diff --git a/lib/cassandra/cluster/schema/fetchers.rb b/lib/cassandra/cluster/schema/fetchers.rb index 993951720..5061043b8 100644 --- a/lib/cassandra/cluster/schema/fetchers.rb +++ b/lib/cassandra/cluster/schema/fetchers.rb @@ -1292,6 +1292,7 @@ def create_table(table_data, rows_columns, rows_indexes, types = nil) is_super = table_flags.include?('super') is_compound = table_flags.include?('compound') is_compact = is_super || is_dense || !is_compound + is_static_compact = !is_super && !is_dense && !is_compound # Separate out partition-key, clustering columns, other columns. partition_key = [] @@ -1303,10 +1304,23 @@ def create_table(table_data, rows_columns, rows_indexes, types = nil) rows_columns.each do |row| next if row['column_name'].empty? - column = create_column(row, types) kind = row['kind'].to_s index = row['position'] || 0 + if is_static_compact + if kind.casecmp('CLUSTERING').zero? || kind.casecmp('REGULAR').zero? + # Skip clustering columns in static-compact tables; they are internal to C*. + # Oddly so are regular columns. + next + end + if kind.casecmp('STATIC').zero? + # Coerce static type to regular. + kind = 'REGULAR' + row['kind'] = 'regular' + end + end + + column = create_column(row, types) case kind.upcase when 'PARTITION_KEY' partition_key[index] = column From e236e540ea018aaf3081bdcdee187e14d4ce6f12 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Tue, 5 Apr 2016 16:11:24 -0700 Subject: [PATCH 029/196] RUBY-186 - Handle custom type columns in C* 3.x schemas. * Updated CHANGELOG * Added integration test. --- CHANGELOG.md | 2 + integration/metadata_test.rb | 37 ++++++++++++++++++- lib/cassandra/cluster/schema/fetchers.rb | 8 +++- .../cluster/schema/fetchers/3.0.0-data.json | 2 +- 4 files changed, 46 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 270d414b8..ef1cdacb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ Features: Bug Fixes: * [RUBY-161] Protocol version negotiation in mixed version clusters should not fall back to v1 unless it is truly warranted. * [RUBY-180] Column ordering is not deterministic in Table metadata. +* [RUBY-185] Internal columns in static-compact and dense tables should be ignored. +* [RUBY-186] Custom type column metadata should be parsed properly for C* 3.x schemas. Breaking Changes: diff --git a/integration/metadata_test.rb b/integration/metadata_test.rb index b56b93b00..0163e2ca0 100644 --- a/integration/metadata_test.rb +++ b/integration/metadata_test.rb @@ -35,6 +35,9 @@ def setup @listener.wait_for_table('simplex', 'blobby') @session.execute("CREATE TABLE simplex.dense (f1 int, f2 int, f3 int, PRIMARY KEY (f1, f2)) WITH COMPACT STORAGE") @listener.wait_for_table('simplex', 'dense') + @session.execute("CREATE TABLE simplex.custom (f1 int PRIMARY KEY," \ + " f2 'org.apache.cassandra.db.marshal.CompositeType(org.apache.cassandra.db.marshal.UUIDType,org.apache.cassandra.db.marshal.UTF8Type)')") + @listener.wait_for_table('simplex', 'custom') end def teardown @@ -59,7 +62,7 @@ def test_can_retrieve_keyspace_metadata assert_equal 1, ks_meta.replication.options['replication_factor'].to_i assert ks_meta.durable_writes? assert ks_meta.has_table?('users') - assert_equal 3, ks_meta.tables.size + assert_equal 4, ks_meta.tables.size ks_cql = Regexp.new(/CREATE KEYSPACE simplex WITH replication = {'class': 'SimpleStrategy', \ 'replication_factor': '1'} AND durable_writes = true;/) @@ -205,6 +208,38 @@ def test_skip_internal_columns_for_dense_table end end + # Test for handling custom type columns in table metadata + # + # test_custom_type_column_in_table tests that a custom type column in a table is processed properly + # when collecting table metadata. + # + # @since 3.0.0 + # @jira_ticket RUBY-186 + # @expected_result the metadata should correctly report the custom type column. + # + # @test_category metadata + # + def test_custom_type_column_in_table + skip("Custom type representation was changed in Cassandra 3.0 to be a single-quoted string") if CCM.cassandra_version < '3.0.0' + assert @cluster.keyspace('simplex').has_table?('custom') + table_meta = @cluster.keyspace('simplex').table('custom') + table_cql = Regexp.new(/CREATE TABLE simplex\.custom \( + f1 int PRIMARY KEY, + f2 'org.apache.cassandra.db.marshal.CompositeType\(org.apache.cassandra.db.marshal.UUIDType,org.apache.cassandra.db.marshal.UTF8Type\)' +\)/) + + assert_equal 0, table_meta.to_cql =~ table_cql, "actual cql: #{table_meta.to_cql}" + assert_equal 2, table_meta.columns.size + column = table_meta.columns[0] + assert_equal 'f1', column.name + assert_equal :int, column.type.kind + column = table_meta.columns[1] + assert_equal 'f2', column.name + assert_equal :custom, column.type.kind + assert_equal 'org.apache.cassandra.db.marshal.CompositeType(org.apache.cassandra.db.marshal.UUIDType,org.apache.cassandra.db.marshal.UTF8Type)', + column.type.name + end + # Test for retrieving crc_check_balance property # # test_table_metadata_contains_crc_check_balance tests that the 'crc_check_balance' property of table metadata is able diff --git a/lib/cassandra/cluster/schema/fetchers.rb b/lib/cassandra/cluster/schema/fetchers.rb index 5061043b8..325354e5e 100644 --- a/lib/cassandra/cluster/schema/fetchers.rb +++ b/lib/cassandra/cluster/schema/fetchers.rb @@ -1278,7 +1278,13 @@ def create_column(column_data, types) name = column_data['column_name'] is_static = column_data['kind'].to_s.casecmp('STATIC').zero? order = column_data['clustering_order'] == 'desc' ? :desc : :asc - type, is_frozen = @type_parser.parse(column_data['type'], types) + if column_data['type'][0] == "'" + # This is a custom column type. + type = Types.custom(column_data['type'].slice(1..-1).chomp("'")) + is_frozen = false + else + type, is_frozen = @type_parser.parse(column_data['type'], types) + end Column.new(name, type, order, is_static, is_frozen) end diff --git a/spec/cassandra/cluster/schema/fetchers/3.0.0-data.json b/spec/cassandra/cluster/schema/fetchers/3.0.0-data.json index 33c64c87a..78971e72c 100644 --- a/spec/cassandra/cluster/schema/fetchers/3.0.0-data.json +++ b/spec/cassandra/cluster/schema/fetchers/3.0.0-data.json @@ -272,7 +272,7 @@ "column_name": "f2", "clustering_order": "none", "column_name_bytes": "f2", - "kind": "regular", + "kind": "static", "position": -1, "type": "int" }, From 9c2df421683ced5bd77c90001f5c4c3cb3e97dae Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Tue, 5 Apr 2016 16:28:47 -0700 Subject: [PATCH 030/196] RUBY-186 - Handle custom type columns in C* 3.x schemas. * Simplified trimming single-quotes in custom type name. --- lib/cassandra/cluster/schema/fetchers.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cassandra/cluster/schema/fetchers.rb b/lib/cassandra/cluster/schema/fetchers.rb index 325354e5e..2c80dbaef 100644 --- a/lib/cassandra/cluster/schema/fetchers.rb +++ b/lib/cassandra/cluster/schema/fetchers.rb @@ -1280,7 +1280,7 @@ def create_column(column_data, types) order = column_data['clustering_order'] == 'desc' ? :desc : :asc if column_data['type'][0] == "'" # This is a custom column type. - type = Types.custom(column_data['type'].slice(1..-1).chomp("'")) + type = Types.custom(column_data['type'].slice(1, column_data['type'].length - 2)) is_frozen = false else type, is_frozen = @type_parser.parse(column_data['type'], types) From 978e25895a870e6e28201dd4706e1668bae65a52 Mon Sep 17 00:00:00 2001 From: Kishan Karunaratne Date: Tue, 5 Apr 2016 17:29:49 -0700 Subject: [PATCH 031/196] [RUBY-167] Integration tests and cucumber features for materialized views --- features/basics/schema_metadata.feature | 19 +- integration/indexes/materialized_view_test.rb | 288 ++++++++++++++++++ integration/integration_test_case.rb | 9 + integration/metadata_test.rb | 18 +- integration/schema_change_listener.rb | 6 + 5 files changed, 326 insertions(+), 14 deletions(-) create mode 100644 integration/indexes/materialized_view_test.rb diff --git a/features/basics/schema_metadata.feature b/features/basics/schema_metadata.feature index 011fb1bcc..897d99422 100644 --- a/features/basics/schema_metadata.feature +++ b/features/basics/schema_metadata.feature @@ -347,12 +347,27 @@ Feature: Schema Metadata cluster = Cassandra.cluster - view = cluster.keyspace('simplex').materialized_view('test_view') - puts view.to_cql + mv_meta = cluster.keyspace('simplex').materialized_view('test_view') + puts "Name: #{mv_meta.name}" + puts "Base table: #{mv_meta.base_table.name}" + puts "Keyspace: #{mv_meta.keyspace.name}" + puts "Partition key: #{mv_meta.partition_key[0].name}" + puts "Clustering column: #{mv_meta.clustering_columns[0].name}" + puts "Num columns: #{mv_meta.columns.size}" + + puts "" + puts mv_meta.to_cql """ When it is executed Then its output should contain: """cql + Name: test_view + Base table: test_table + Keyspace: simplex + Partition key: f1 + Clustering column: f2 + Num columns: 2 + CREATE MATERIALIZED VIEW simplex.test_view AS SELECT "f1", "f2" FROM simplex.test_table diff --git a/integration/indexes/materialized_view_test.rb b/integration/indexes/materialized_view_test.rb new file mode 100644 index 000000000..c7013b1cf --- /dev/null +++ b/integration/indexes/materialized_view_test.rb @@ -0,0 +1,288 @@ +# encoding: utf-8 + +#-- +# Copyright 2013-2016 DataStax, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + +require File.dirname(__FILE__) + '/../integration_test_case.rb' + +class MaterializedViewTest < IntegrationTestCase + + def setup + return if CCM.cassandra_version < '3.0.0' + + @@ccm_cluster.setup_schema("CREATE KEYSPACE simplex WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}") + + @cluster = Cassandra.cluster( + schema_refresh_delay: 0.1, + schema_refresh_timeout: 0.1 + ) + @listener = SchemaChangeListener.new(@cluster) + @session = @cluster.connect('simplex') + + @session.execute("CREATE TABLE simplex.scores( + user TEXT, + game TEXT, + year INT, + month INT, + day INT, + score INT, + PRIMARY KEY (user, game, year, month, day) + )") + @session.execute("CREATE MATERIALIZED VIEW simplex.monthlyhigh AS + SELECT game, year, month, score, user, day FROM simplex.scores + WHERE game IS NOT NULL AND year IS NOT NULL AND month IS NOT NULL AND score IS NOT NULL AND user IS NOT NULL AND day IS NOT NULL + PRIMARY KEY ((game, year, month), score, user, day) + WITH CLUSTERING ORDER BY (score DESC, user ASC, day ASC)") + @listener.wait_for_materialized_view('simplex', 'monthlyhigh') + end + + def teardown + @cluster && @cluster.close + end + + # Test for retrieving materialized view metadata + # + # test_can_retrieve_materialized_view_metadata tests that all pieces of materialized view metadata can be retrieved. + # It goes through each piece of materialized view metadata and verifies that each piece is as expected. + # + # @since 3.0.0 + # @jira_ticket RUBY-167 + # @expected_result materialized view metadata should be retrieved. + # + # @test_category materialized_view + # + def test_can_retrieve_materialized_view_metadata + skip("Materialized views were introduced in Cassandra 3.0.0") if CCM.cassandra_version < '3.0.0' + + assert @cluster.keyspace('simplex').has_materialized_view?('monthlyhigh') + mv_meta = @cluster.keyspace('simplex').materialized_view('monthlyhigh') + + assert_equal 'monthlyhigh', mv_meta.name + refute_nil mv_meta.id + assert_equal 'scores', mv_meta.base_table.name + assert_equal 'simplex', mv_meta.keyspace.name + refute_nil mv_meta.options + + assert_columns([['game', :text], ['year', :int], ['month', :int], ['score', :int], ['user', :text], ['day', :int]], + mv_meta.primary_key) + assert_columns([['game', :text], ['year', :int], ['month', :int]], mv_meta.partition_key) + assert_columns([['score', :int], ['user', :text], ['day', :int]], mv_meta.clustering_columns) + + assert_equal 6, mv_meta.columns.size + mv_meta.each_column do |column| + assert ['game', 'year', 'month', 'score', 'user', 'day'].any? { |name| name == column.name } + assert [:text, :int].any? { |type| type == column.type.kind } + end + end + + # Test for retrieving mv metadata with quoted identifiers + # + # test_can_retrieve_quoted_mv_metadata tests that all pieces of materialized view metadata can be retrieved, when the + # view has quoted identifiers. It goes through each piece of materialized view metadata and verifies that each piece + # is as expected. + # + # @since 3.0.0 + # @jira_ticket RUBY-167 + # @expected_result mv metadata with quoted identifiers should be retrieved. + # + # @test_category materialized_view + # + def test_can_retrieve_quoted_mv_metadata + skip("Materialized views were introduced in Cassandra 3.0.0") if CCM.cassandra_version < '3.0.0' + + @session.execute("CREATE TABLE simplex.test ( + \"theKey\" int, + \"the;Clustering\" int, + \"the Value\" int, + PRIMARY KEY (\"theKey\", \"the;Clustering\"))") + @session.execute("CREATE MATERIALIZED VIEW simplex.mv1 AS + SELECT \"theKey\", \"the;Clustering\", \"the Value\" + FROM simplex.test + WHERE \"theKey\" IS NOT NULL AND \"the;Clustering\" IS NOT NULL AND \"the Value\" IS NOT NULL + PRIMARY KEY (\"theKey\", \"the;Clustering\")") + + @listener.wait_for_materialized_view('simplex', 'mv1') + + assert @cluster.keyspace('simplex').has_materialized_view?('mv1') + mv_meta = @cluster.keyspace('simplex').materialized_view('mv1') + + assert_equal 'mv1', mv_meta.name + refute_nil mv_meta.id + assert_equal 'test', mv_meta.base_table.name + assert_equal 'simplex', mv_meta.keyspace.name + refute_nil mv_meta.options + + assert_columns([['theKey', :int], ['the;Clustering', :int]], mv_meta.primary_key) + assert_columns([['theKey', :int]], mv_meta.partition_key) + assert_columns([['the;Clustering', :int]], mv_meta.clustering_columns) + + assert_equal 3, mv_meta.columns.size + mv_meta.each_column do |column| + assert ['theKey', 'the;Clustering', 'the Value'].any? { |name| name == column.name } + assert_equal :int, column.type.kind + end + end + + # Test for column ordering in materialized view metadata + # + # test_column_ordering_is_deterministic tests that the metadata relating to the columns are retrieved in the proper + # order. This proper order is: partition key, clustering columns, and then all other columns alphanumerically. + # + # @since 3.0.0 + # @jira_ticket RUBY-167 + # @expected_result column ordering should be correct in materialized view metadata. + # + # @test_category materialized_view + # + def test_column_ordering_is_deterministic + skip("Materialized views were introduced in Cassandra 3.0.0") if CCM.cassandra_version < '3.0.0' + + assert @cluster.keyspace('simplex').has_materialized_view?('monthlyhigh') + mv_cql = Regexp.new(/CREATE MATERIALIZED VIEW simplex.monthlyhigh AS +SELECT game, year, month, score, user, day +FROM simplex.scores +WHERE game IS NOT NULL AND year IS NOT NULL AND month IS NOT NULL AND score IS NOT NULL AND user IS NOT NULL AND day IS NOT NULL +PRIMARY KEY \(\(game, year, month\), score, user, day\) +WITH bloom_filter_fp_chance = 0.01 + AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} + AND comment = '' + AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} + AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} + AND crc_check_chance = 1.0 + AND dclocal_read_repair_chance = 0.1 + AND default_time_to_live = 0 + AND gc_grace_seconds = 864000 + AND max_index_interval = 2048 + AND memtable_flush_period_in_ms = 0 + AND min_index_interval = 128 + AND read_repair_chance = 0.0 + AND speculative_retry = '99PERCENTILE';/) + + mv_meta = @cluster.keyspace('simplex').materialized_view('monthlyhigh') + assert_match mv_cql, mv_meta.to_cql + + col_names = ['game', 'year', 'month', 'score', 'user', 'day'] + mv_meta.each_column do |column| + assert_equal col_names[0], column.name + col_names.delete_at(0) + end + end + + # Test for retrieving mv metadata updates + # + # test_materialized_view_metadata_updates tests that materialized view metadata is updated when there is any update + # to the materialized view. It first creates a simple materialized view and verifies that the default compaction + # strategy is SizeTieredCompactionStrategy. It then alters the compaction strategy to LeveledCompactionStrategy and + # verifies that the metadata is properly updated. + # + # @since 3.0.0 + # @jira_ticket RUBY-167 + # @expected_result mv metadata should be updated when the mv is updated. + # + # @test_category materialized_view + # + def test_materialized_view_metadata_updates + skip("Materialized views were introduced in Cassandra 3.0.0") if CCM.cassandra_version < '3.0.0' + + @session.execute("CREATE TABLE simplex.test (pk int PRIMARY KEY, c int)") + @session.execute("CREATE MATERIALIZED VIEW simplex.mv1 AS SELECT c FROM simplex.test WHERE c IS NOT NULL PRIMARY KEY (pk, c)") + + @listener.wait_for_materialized_view('simplex', 'mv1') + + assert @cluster.keyspace('simplex').has_materialized_view?('mv1') + mv_meta = @cluster.keyspace('simplex').materialized_view('mv1') + assert_equal 'SizeTieredCompactionStrategy', mv_meta.options.compaction_strategy.class_name + + @session.execute("ALTER MATERIALIZED VIEW simplex.mv1 WITH compaction = { 'class' : 'LeveledCompactionStrategy' }") + sleep(2) + mv_meta = @cluster.keyspace('simplex').materialized_view('mv1') + assert_equal 'LeveledCompactionStrategy', mv_meta.options.compaction_strategy.class_name + end + + # Test for retrieving mv metadata drops + # + # test_materialized_view_metadata_drop tests that materialized view metadata is removed when the materialized view + # is dropped. It first creates a simple materialized view and verifies that its metadata can be accessed. It then + # drops this materialized view and verifies that the metadata for the view no longer exists. + # + # @since 3.0.0 + # @jira_ticket RUBY-167 + # @expected_result mv metadata should be removed when the mv is dropped. + # + # @test_category materialized_view + # + def test_materialized_view_metadata_drop + skip("Materialized views were introduced in Cassandra 3.0.0") if CCM.cassandra_version < '3.0.0' + + @session.execute("CREATE TABLE simplex.test (pk int PRIMARY KEY, c int)") + @session.execute("CREATE MATERIALIZED VIEW simplex.mv1 AS SELECT c FROM simplex.test WHERE c IS NOT NULL PRIMARY KEY (pk, c)") + + @listener.wait_for_materialized_view('simplex', 'mv1') + assert @cluster.keyspace('simplex').has_materialized_view?('mv1') + + @session.execute("DROP MATERIALIZED VIEW simplex.mv1") + sleep(2) + refute @cluster.keyspace('simplex').has_materialized_view?('mv1') + end + + # Test for retrieving mv metadata updates from base table changes + # + # test_base_table_column_addition tests that materialized view metadata is properly updated when there is a change to + # the underlying base table. It first creates a basic table and a materialized view. It then alters the table to add + # a new column, verifying that the alteration is in the table's metadata. Finally, it verifies that the alteration + # has been propagated to the materialized view's metadata as well. + # + # @since 3.0.0 + # @jira_ticket RUBY-167 + # @expected_result mv metadata should be updated when the base table is updated. + # + # @test_category materialized_view + # + def test_base_table_column_addition + skip("Materialized views were introduced in Cassandra 3.0.0") if CCM.cassandra_version < '3.0.0' + + @session.execute("CREATE TABLE simplex.scores2( + user TEXT, + game TEXT, + year INT, + month INT, + day INT, + score INT, + PRIMARY KEY (user, game, year, month, day) + )") + @session.execute("CREATE MATERIALIZED VIEW simplex.alltimehigh AS + SELECT * FROM simplex.scores2 + WHERE game IS NOT NULL AND score IS NOT NULL AND user IS NOT NULL AND year IS NOT NULL AND month IS NOT NULL AND day IS NOT NULL + PRIMARY KEY (game, score, user, year, month, day) + WITH CLUSTERING ORDER BY (score DESC)") + + @listener.wait_for_materialized_view('simplex', 'alltimehigh') + + assert @cluster.keyspace('simplex').has_materialized_view?('alltimehigh') + mv_meta = @cluster.keyspace('simplex').materialized_view('alltimehigh') + refute mv_meta.has_column?('fouls') + + @session.execute("ALTER TABLE simplex.scores2 ADD fouls INT") + + table_meta = @cluster.keyspace('simplex').table('scores2') + assert table_meta.has_column?('fouls') + assert_equal :int, table_meta.column('fouls').type.kind + + mv_meta = @cluster.keyspace('simplex').materialized_view('alltimehigh') + assert mv_meta.has_column?('fouls') + assert_equal :int, mv_meta.column('fouls').type.kind + end +end diff --git a/integration/integration_test_case.rb b/integration/integration_test_case.rb index 28a0d0bc9..91651e34e 100644 --- a/integration/integration_test_case.rb +++ b/integration/integration_test_case.rb @@ -38,6 +38,15 @@ def self.after_suite def before_setup puts ANSI::Code.magenta("\n===== Begin #{self.__name__} ====") end + + def assert_columns(expected_names_and_types, actual_columns) + assert_equal(expected_names_and_types.size, actual_columns.size) + + expected_names_and_types.zip(actual_columns) do |expected, actual_column| + assert_equal expected[0], actual_column.name + assert_equal expected[1], actual_column.type.kind + end + end end class IntegrationUnit < MiniTest::Unit diff --git a/integration/metadata_test.rb b/integration/metadata_test.rb index 0163e2ca0..2cf28bd3c 100644 --- a/integration/metadata_test.rb +++ b/integration/metadata_test.rb @@ -90,18 +90,9 @@ def test_can_retrieve_table_metadata refute_nil table_meta.id unless CCM.cassandra_version < '3.0.0' refute_nil table_meta.options - assert_equal 2, table_meta.primary_key.size - assert_equal 'user_id', table_meta.primary_key[0].name - assert_equal :bigint, table_meta.primary_key[0].type.kind - assert_equal 'last', table_meta.primary_key[1].name - assert_equal :text, table_meta.primary_key[1].type.kind - - assert_equal 1, table_meta.partition_key.size - assert_equal 'user_id', table_meta.partition_key[0].name - assert_equal :bigint, table_meta.partition_key[0].type.kind - assert_equal 1, table_meta.clustering_columns.size - assert_equal 'last', table_meta.clustering_columns[0].name - assert_equal :text, table_meta.clustering_columns[0].type.kind + assert_columns([['user_id', :bigint], ['last', :text]], table_meta.primary_key) + assert_columns([['user_id', :bigint]], table_meta.partition_key) + assert_columns([ ['last', :text]], table_meta.clustering_columns) assert_equal :asc, table_meta.clustering_order.first assert_equal 4, table_meta.columns.size @@ -148,6 +139,7 @@ def test_column_ordering_is_deterministic # column and "value blob" regular columns are excluded from table metadata for static-compact tables. # It also coerces columns marked static to be regular instead. # + # @since 3.0.0 # @jira_ticket RUBY-185 # @expected_result the metadata should only report columns we've consciously added to the table. # @@ -177,6 +169,7 @@ def test_skip_internal_columns_for_static_compact_table # test_skip_internal_columns_for_dense_table tests that "value " column is excluded from table metadata # for dense tables. # + # @since 3.0.0 # @jira_ticket RUBY-185 # @expected_result the metadata should only report columns we've consciously added to the table. # @@ -221,6 +214,7 @@ def test_skip_internal_columns_for_dense_table # def test_custom_type_column_in_table skip("Custom type representation was changed in Cassandra 3.0 to be a single-quoted string") if CCM.cassandra_version < '3.0.0' + assert @cluster.keyspace('simplex').has_table?('custom') table_meta = @cluster.keyspace('simplex').table('custom') table_cql = Regexp.new(/CREATE TABLE simplex\.custom \( diff --git a/integration/schema_change_listener.rb b/integration/schema_change_listener.rb index 6f8958a6f..114e5655c 100644 --- a/integration/schema_change_listener.rb +++ b/integration/schema_change_listener.rb @@ -66,6 +66,12 @@ def wait_for_index(keyspace_name, table_name, index_name, *args) end end + def wait_for_materialized_view(keyspace_name, view_name, *args) + wait_for_change(keyspace_name, 2) do |ks| + ks.has_materialized_view?(view_name, *args) + end + end + def keyspace_changed(keyspace) # This looks a little strange, but here's the idea: if we don't have # Condition's for this keyspace, immediately return. Otherwise, for each From c6d108b43d481595318876de777540d3b024ed66 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Wed, 6 Apr 2016 14:13:07 -0700 Subject: [PATCH 032/196] RUBY-199 * Bumped version to 3.0.1 * Tiny refactoring of Cluster to support creating a different type of Session. * Tiny refactoring of Driver to support creating a different type of Cluster. --- Gemfile.lock | 2 +- lib/cassandra.rb | 53 +++++++++++++++++++++------------------- lib/cassandra/cluster.rb | 8 +++++- lib/cassandra/driver.rb | 32 ++++++++++++------------ lib/cassandra/version.rb | 2 +- spec/cassandra_spec.rb | 6 +++-- 6 files changed, 58 insertions(+), 45 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 77ecb61af..e3531d49c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - cassandra-driver (3.0.0.rc.1) + cassandra-driver (3.0.1) ione (~> 1.2) GEM diff --git a/lib/cassandra.rb b/lib/cassandra.rb index edf94a68c..fe34bec7e 100644 --- a/lib/cassandra.rb +++ b/lib/cassandra.rb @@ -290,28 +290,7 @@ def self.cluster(options = {}) # @return [Cassandra::Future] a future resolving to the # cluster instance. def self.cluster_async(options = {}) - options = validate_and_massage_options(options) - hosts = [] - - Array(options.fetch(:hosts, '127.0.0.1')).each do |host| - case host - when ::IPAddr - hosts << host - when ::String # ip address or hostname - Resolv.each_address(host) do |ip| - hosts << ::IPAddr.new(ip) - end - else - raise ::ArgumentError, ":hosts must be String or IPAddr, #{host.inspect} given" - end - end - - if hosts.empty? - raise ::ArgumentError, - ":hosts #{options[:hosts].inspect} could not be resolved to any ip address" - end - - hosts.shuffle! + options, hosts = validate_and_massage_options(options) rescue => e futures = options.fetch(:futures_factory) { return Future::Error.new(e) } futures.error(e) @@ -661,14 +640,14 @@ def self.validate_and_massage_options(options) case address_resolution when :none - # do nothing + # do nothing when :ec2_multi_region options[:address_resolution_policy] = AddressResolution::Policies::EC2MultiRegion.new else raise ::ArgumentError, ':address_resolution must be either :none or :ec2_multi_region, ' \ - "#{address_resolution.inspect} given" + "#{address_resolution.inspect} given" end end @@ -726,7 +705,31 @@ def self.validate_and_massage_options(options) end end end - options + + # Get host addresses. + hosts = [] + + Array(options.fetch(:hosts, '127.0.0.1')).each do |host| + case host + when ::IPAddr + hosts << host + when ::String # ip address or hostname + Resolv.each_address(host) do |ip| + hosts << ::IPAddr.new(ip) + end + else + raise ::ArgumentError, ":hosts must be String or IPAddr, #{host.inspect} given" + end + end + + if hosts.empty? + raise ::ArgumentError, + ":hosts #{options[:hosts].inspect} could not be resolved to any ip address" + end + + hosts.shuffle! + + [options, hosts] end # @private diff --git a/lib/cassandra/cluster.rb b/lib/cassandra/cluster.rb index 03a80a07e..22d81370d 100644 --- a/lib/cassandra/cluster.rb +++ b/lib/cassandra/cluster.rb @@ -205,7 +205,7 @@ def connect_async(keyspace = nil) @address_resolver, @connection_options, @futures) - session = Session.new(client, @execution_options, @futures) + session = new_session(client) promise = @futures.promise client.connect.on_complete do |f| @@ -277,6 +277,12 @@ def close def inspect "#<#{self.class.name}:0x#{object_id.to_s(16)}>" end + + protected + + def new_session(client) + Cassandra::Session.new(client, @execution_options, @futures) + end end end diff --git a/lib/cassandra/driver.rb b/lib/cassandra/driver.rb index aea712883..5e6126989 100644 --- a/lib/cassandra/driver.rb +++ b/lib/cassandra/driver.rb @@ -92,22 +92,24 @@ def self.let(name, &block) schema_fetcher) end + let(:cluster_klass) { Cluster } + let(:cluster) do - Cluster.new(logger, - io_reactor, - executor, - control_connection, - cluster_registry, - cluster_schema, - cluster_metadata, - execution_options, - connection_options, - load_balancing_policy, - reconnection_policy, - retry_policy, - address_resolution_policy, - connector, - futures_factory) + cluster_klass.new(logger, + io_reactor, + executor, + control_connection, + cluster_registry, + cluster_schema, + cluster_metadata, + execution_options, + connection_options, + load_balancing_policy, + reconnection_policy, + retry_policy, + address_resolution_policy, + connector, + futures_factory) end let(:execution_options) do diff --git a/lib/cassandra/version.rb b/lib/cassandra/version.rb index 0feb69088..3a04c0bbe 100644 --- a/lib/cassandra/version.rb +++ b/lib/cassandra/version.rb @@ -17,5 +17,5 @@ #++ module Cassandra - VERSION = '3.0.0.rc.1'.freeze + VERSION = '3.0.1'.freeze end diff --git a/spec/cassandra_spec.rb b/spec/cassandra_spec.rb index 5b91e28fb..55476153e 100644 --- a/spec/cassandra_spec.rb +++ b/spec/cassandra_spec.rb @@ -8,8 +8,10 @@ # noinspection RubyClassModuleNamingConvention module C class << self - # noinspection RubyResolve - alias :validate :validate_and_massage_options + def validate(*args) + # Actually, we only care about option-validation, and the method returns options + hosts. + validate_and_massage_options(*args).first + end end end From 24b4341b90fe412e2e10504e0adf9d0300fccecb Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 11 Apr 2016 10:05:10 -0700 Subject: [PATCH 033/196] Prep for 3.0.0 rc2 * Updated CHANGELOG and README to reflect changes. * Updated version number in version.rb. --- CHANGELOG.md | 3 ++- Gemfile.lock | 2 +- README.md | 4 ++-- lib/cassandra/version.rb | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef1cdacb0..f0a6c68b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# master +# 3.0.0 rc2 Features: * Add protocol_version configuration option to allow the user to force the protocol version to use for communication with nodes. * Expose listen_address and broadcast_address in `Cassandra::Host` if available. @@ -6,6 +6,7 @@ Features: * Add support for Cassandra indexes in the schema metadata. * Add or expose the id, options, keyspace, partition_key, clustering_columns, and clustering_order attributes to table and view schema objects. * Add crc_check_chance and extensions attributes to ColumnContainer options. +* Make cluster configuration options list publicly available. (Thanks, Evan Prothro!) Bug Fixes: * [RUBY-161] Protocol version negotiation in mixed version clusters should not fall back to v1 unless it is truly warranted. diff --git a/Gemfile.lock b/Gemfile.lock index 77ecb61af..d843c739c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - cassandra-driver (3.0.0.rc.1) + cassandra-driver (3.0.0.rc.2) ione (~> 1.2) GEM diff --git a/README.md b/README.md index f41cfe097..fae0ff46c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Datastax Ruby Driver for Apache Cassandra -*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. v1.0.0-beta.3](https://github.com/datastax/ruby-driver/tree/v1.0.0-beta.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).* [![Build Status](https://travis-ci.org/datastax/ruby-driver.svg?branch=master)](https://travis-ci.org/datastax/ruby-driver) @@ -108,7 +108,7 @@ Some of the new features added to the driver have unfortunately led to changes i * Add connections_per_local_node, connections_per_remote_node, requests_per_connection cluster configuration options to tune parallel query execution and resource usage. * Add Cassandra::Logger class to make it easy for users to enable debug logging in the client. * Add protocol_version configuration option to allow the user to force the protocol version to use for communication with nodes. -* Add support for materialized views in the schema metadata. +* Add support for materialized views and indexes in the schema metadata. ### Breaking Changes: diff --git a/lib/cassandra/version.rb b/lib/cassandra/version.rb index 0feb69088..25cbd4fa8 100644 --- a/lib/cassandra/version.rb +++ b/lib/cassandra/version.rb @@ -17,5 +17,5 @@ #++ module Cassandra - VERSION = '3.0.0.rc.1'.freeze + VERSION = '3.0.0.rc.2'.freeze end From c7438c86263c2e78b095c1a44309a59427547a47 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 11 Apr 2016 10:13:34 -0700 Subject: [PATCH 034/196] Updated docs.yaml with sha for rc2. --- docs.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs.yaml b/docs.yaml index 9c42eb5ca..ebd21b051 100644 --- a/docs.yaml +++ b/docs.yaml @@ -37,8 +37,8 @@ links: - title: Releases href: https://github.com/datastax/ruby-driver/releases versions: - - name: v3.0.0.rc.1 - ref: c8ed32ce85b608f172e72133dd68c3e2af34db00 + - name: v3.0.0.rc.2 + ref: 24b4341b90fe412e2e10504e0adf9d0300fccecb - name: v3.0.0.beta.1 ref: a0a5f7a5cc308497e5e865f008130441722208e5 - name: v2.1.5 From e3c462cd4d1eeb05c501125601a5970d17243d5e Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 11 Apr 2016 12:04:53 -0700 Subject: [PATCH 035/196] * Added master section to CHANGELOG for the next version. * Removed 3.0.0 beta version from docs.yaml. --- CHANGELOG.md | 7 +++++-- docs.yaml | 2 -- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0a6c68b1..a1d3a29ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# master +Features: + +Bug Fixes: + # 3.0.0 rc2 Features: * Add protocol_version configuration option to allow the user to force the protocol version to use for communication with nodes. @@ -14,8 +19,6 @@ Bug Fixes: * [RUBY-185] Internal columns in static-compact and dense tables should be ignored. * [RUBY-186] Custom type column metadata should be parsed properly for C* 3.x schemas. -Breaking Changes: - # 3.0.0 rc1 Features: diff --git a/docs.yaml b/docs.yaml index ebd21b051..1b48aff89 100644 --- a/docs.yaml +++ b/docs.yaml @@ -39,8 +39,6 @@ links: versions: - name: v3.0.0.rc.2 ref: 24b4341b90fe412e2e10504e0adf9d0300fccecb - - name: v3.0.0.beta.1 - ref: a0a5f7a5cc308497e5e865f008130441722208e5 - name: v2.1.5 ref: bbb7084d11be701ff42f298373c0255b9a3e2b7f - name: v2.0.1 From 7c934fd6cf74e62cb321df61f2473e6d8b369df9 Mon Sep 17 00:00:00 2001 From: Kishan Karunaratne Date: Mon, 11 Apr 2016 13:30:55 -0700 Subject: [PATCH 036/196] Add Indexes test to Rakefile --- Rakefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index 6966aa898..93c0a6e72 100644 --- a/Rakefile +++ b/Rakefile @@ -35,7 +35,8 @@ Rake::TestTask.new(:integration => :compile) do |t| 'integration/security/*_test.rb', 'integration/load_balancing/*_test.rb', 'integration/types/*_test.rb', - 'integration/functions/*_test.rb'] + 'integration/functions/*_test.rb', + 'integration/indexes/*_test.rb'] t.verbose = true end From 6eac4ed5ebb184110affed258155e4ec76a45f6b Mon Sep 17 00:00:00 2001 From: Kishan Karunaratne Date: Mon, 11 Apr 2016 14:21:23 -0700 Subject: [PATCH 037/196] Bugfixes in indexes tests --- integration/indexes/indexes_test.rb | 3 ++- integration/indexes/materialized_view_test.rb | 5 +++-- integration/schema_change_listener.rb | 4 +++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/integration/indexes/indexes_test.rb b/integration/indexes/indexes_test.rb index fb1316307..68135cb77 100644 --- a/integration/indexes/indexes_test.rb +++ b/integration/indexes/indexes_test.rb @@ -89,6 +89,7 @@ def test_can_create_index_on_partial_collections assert_equal 'keys(b)', index.target @session.execute("DROP INDEX b_index") + @cluster.refresh_schema @session.execute("CREATE INDEX b_index ON simplex.collection_test (b)") @listener.wait_for_index('simplex', 'collection_test', 'b_index') @@ -118,7 +119,7 @@ def test_can_create_index_on_partial_collections # @test_category indexes # def test_can_create_index_on_full_collections - skip("Secondary index on full collections were introduced in Cassandra 2.1.3") if CCM.cassandra_version.to_i < '2.1.3'.to_i + skip("Secondary index on full collections were introduced in Cassandra 2.1.3") if Gem::Version.new(CCM.cassandra_version) < Gem::Version.new('2.1.3') @session.execute("CREATE TABLE simplex.collection_test (a int PRIMARY KEY, b frozen>)") @session.execute("CREATE INDEX b_index ON simplex.collection_test (full(b))") diff --git a/integration/indexes/materialized_view_test.rb b/integration/indexes/materialized_view_test.rb index c7013b1cf..ef5bd55cf 100644 --- a/integration/indexes/materialized_view_test.rb +++ b/integration/indexes/materialized_view_test.rb @@ -207,7 +207,7 @@ def test_materialized_view_metadata_updates assert_equal 'SizeTieredCompactionStrategy', mv_meta.options.compaction_strategy.class_name @session.execute("ALTER MATERIALIZED VIEW simplex.mv1 WITH compaction = { 'class' : 'LeveledCompactionStrategy' }") - sleep(2) + @cluster.refresh_schema mv_meta = @cluster.keyspace('simplex').materialized_view('mv1') assert_equal 'LeveledCompactionStrategy', mv_meta.options.compaction_strategy.class_name end @@ -234,7 +234,7 @@ def test_materialized_view_metadata_drop assert @cluster.keyspace('simplex').has_materialized_view?('mv1') @session.execute("DROP MATERIALIZED VIEW simplex.mv1") - sleep(2) + @cluster.refresh_schema refute @cluster.keyspace('simplex').has_materialized_view?('mv1') end @@ -276,6 +276,7 @@ def test_base_table_column_addition refute mv_meta.has_column?('fouls') @session.execute("ALTER TABLE simplex.scores2 ADD fouls INT") + @cluster.refresh_schema table_meta = @cluster.keyspace('simplex').table('scores2') assert table_meta.has_column?('fouls') diff --git a/integration/schema_change_listener.rb b/integration/schema_change_listener.rb index 114e5655c..1264b7333 100644 --- a/integration/schema_change_listener.rb +++ b/integration/schema_change_listener.rb @@ -61,6 +61,7 @@ def wait_for_table(keyspace_name, table_name, *args) end def wait_for_index(keyspace_name, table_name, index_name, *args) + self.wait_for_table(keyspace_name, table_name) wait_for_change(keyspace_name, 2) do |ks| ks.table(table_name).has_index?(index_name, *args) end @@ -79,4 +80,5 @@ def keyspace_changed(keyspace) @conditions.fetch(keyspace.name) { return }.each { |c| c.evaluate(keyspace) } nil end -end \ No newline at end of file +end + From f53b4b504c3c0346cc52b28b2bf2eb1a9a072fb2 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 18 Apr 2016 10:24:29 -0700 Subject: [PATCH 038/196] * Reverted change in cluster.rb where we factored out session creation to a method that could be overridden in sub-classes. This is no longer needed. --- lib/cassandra/cluster.rb | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/lib/cassandra/cluster.rb b/lib/cassandra/cluster.rb index 22d81370d..03a80a07e 100644 --- a/lib/cassandra/cluster.rb +++ b/lib/cassandra/cluster.rb @@ -205,7 +205,7 @@ def connect_async(keyspace = nil) @address_resolver, @connection_options, @futures) - session = new_session(client) + session = Session.new(client, @execution_options, @futures) promise = @futures.promise client.connect.on_complete do |f| @@ -277,12 +277,6 @@ def close def inspect "#<#{self.class.name}:0x#{object_id.to_s(16)}>" end - - protected - - def new_session(client) - Cassandra::Session.new(client, @execution_options, @futures) - end end end From bed9ac8b171e32f8edfd142e77202aefb58b9c5a Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 18 Apr 2016 15:22:29 -0700 Subject: [PATCH 039/196] * Clarification of Result.next_page_async return value description. --- lib/cassandra/result.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/cassandra/result.rb b/lib/cassandra/result.rb index 22e43f557..66fd926ba 100644 --- a/lib/cassandra/result.rb +++ b/lib/cassandra/result.rb @@ -76,8 +76,8 @@ def next_page(options = nil) # # @note `:paging_state` option will be ignored. # - # @return [Cassandra::Future] `nil` if last - # page + # @return [Cassandra::Future] a future that resolves to a new Result if there is a new page, + # `nil` otherwise. # # @see Cassandra::Session#execute def next_page_async(options = nil) From 70614782edd09c36e44580a36f6db83a5c0c014f Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Tue, 19 Apr 2016 17:55:41 -0700 Subject: [PATCH 040/196] * Refactored Statement classes and session.execute_async to make it possible to create new Statement classes (say in DSE) that the core driver can execute. --- lib/cassandra/session.rb | 21 +++++++-------------- lib/cassandra/statement.rb | 5 +++++ lib/cassandra/statements/batch.rb | 6 ++++++ lib/cassandra/statements/bound.rb | 5 +++++ lib/cassandra/statements/prepared.rb | 5 +++++ lib/cassandra/statements/simple.rb | 5 +++++ lib/cassandra/statements/void.rb | 5 +++++ spec/cassandra/session_spec.rb | 8 ++++---- 8 files changed, 42 insertions(+), 18 deletions(-) diff --git a/lib/cassandra/session.rb b/lib/cassandra/session.rb index cf9427de9..3132aac39 100644 --- a/lib/cassandra/session.rb +++ b/lib/cassandra/session.rb @@ -89,20 +89,13 @@ def execute_async(statement, options = nil) case statement when ::String - @client.query(Statements::Simple.new(statement, - options.arguments, - options.type_hints, - options.idempotent?), - options) - when Statements::Simple - @client.query(statement, options) - when Statements::Prepared - @client.execute(statement.bind(options.arguments), options) - when Statements::Bound - @client.execute(statement, options) - when Statements::Batch - Util.assert_not_empty(statement.statements) { 'batch cannot be empty' } - @client.batch(statement, options) + Statements::Simple.new(statement, + options.arguments, + options.type_hints, + options.idempotent?).accept(@client, + options) + when Statement + statement.accept(@client, options) else @futures.error(::ArgumentError.new("unsupported statement #{statement.inspect}")) end diff --git a/lib/cassandra/statement.rb b/lib/cassandra/statement.rb index 785e17f53..73071a77f 100644 --- a/lib/cassandra/statement.rb +++ b/lib/cassandra/statement.rb @@ -23,5 +23,10 @@ module Statement def idempotent? !!@idempotent end + + # @private + def accept(client, options) + raise NotImplementedError, "#{self.class} must implement :accept method" + end end end diff --git a/lib/cassandra/statements/batch.rb b/lib/cassandra/statements/batch.rb index c40ae9442..f681ec1b4 100644 --- a/lib/cassandra/statements/batch.rb +++ b/lib/cassandra/statements/batch.rb @@ -113,6 +113,12 @@ def add(statement, options = nil) self end + # @private + def accept(client, options) + Util.assert_not_empty(statements) { 'batch cannot be empty' } + client.batch(self, options) + end + # Determines whether or not the statement is safe to retry on timeout # Batches are idempotent only when all statements in a batch are. # @return [Boolean] whether the statement is safe to retry on timeout diff --git a/lib/cassandra/statements/bound.rb b/lib/cassandra/statements/bound.rb index 4e4f7c875..afd70cc8d 100644 --- a/lib/cassandra/statements/bound.rb +++ b/lib/cassandra/statements/bound.rb @@ -46,6 +46,11 @@ def initialize(cql, @idempotent = idempotent end + # @private + def accept(client, options) + client.execute(self, options) + end + # @return [String] a CLI-friendly bound statement representation def inspect "#<#{self.class.name}:0x#{object_id.to_s(16)} @cql=#{@cql.inspect} " \ diff --git a/lib/cassandra/statements/prepared.rb b/lib/cassandra/statements/prepared.rb index 2f98294a3..822840943 100644 --- a/lib/cassandra/statements/prepared.rb +++ b/lib/cassandra/statements/prepared.rb @@ -155,6 +155,11 @@ def execution_info nil) end + # @private + def accept(client, options) + client.execute(bind(options.arguments), options) + end + # @return [String] a CLI-friendly prepared statement representation def inspect "#<#{self.class.name}:0x#{object_id.to_s(16)} @cql=#{@cql.inspect}>" diff --git a/lib/cassandra/statements/simple.rb b/lib/cassandra/statements/simple.rb index d0dcd8b4b..ef2adff1e 100644 --- a/lib/cassandra/statements/simple.rb +++ b/lib/cassandra/statements/simple.rb @@ -93,6 +93,11 @@ def initialize(cql, params = nil, type_hints = nil, idempotent = false) @idempotent = idempotent end + # @private + def accept(client, options) + client.query(self, options) + end + # @return [String] a CLI-friendly simple statement representation def inspect "#<#{self.class.name}:0x#{object_id.to_s(16)} @cql=#{@cql.inspect} " \ diff --git a/lib/cassandra/statements/void.rb b/lib/cassandra/statements/void.rb index f4435e82b..942ada1f9 100644 --- a/lib/cassandra/statements/void.rb +++ b/lib/cassandra/statements/void.rb @@ -23,6 +23,11 @@ module Statements class Void include Statement + # @private + def accept(client, options) + nil + end + # Returns nothing # @return [nil] there is no cql for the void statement def cql diff --git a/spec/cassandra/session_spec.rb b/spec/cassandra/session_spec.rb index f9e119e59..5fca81917 100644 --- a/spec/cassandra/session_spec.rb +++ b/spec/cassandra/session_spec.rb @@ -35,7 +35,7 @@ module Cassandra statement = double('simple statement') expect(Statements::Simple).to receive(:new).once.with(cql, EMPTY_LIST, EMPTY_LIST, false).and_return(statement) - expect(client).to receive(:query).once.with(statement, session_options).and_return(promise) + expect(statement).to receive(:accept).once.with(client, session_options).and_return(promise) expect(session.execute_async(cql)).to eq(promise) end end @@ -48,7 +48,7 @@ module Cassandra statement = double('simple statement') expect(Statements::Simple).to receive(:new).once.with(cql, [1], [], false).and_return(statement) - expect(client).to receive(:query).once.with(statement, session_options.override(arguments: [1])).and_return(promise) + expect(statement).to receive(:accept).once.with(client, session_options.override(arguments: [1])).and_return(promise) expect(session.execute_async(cql, arguments: [1])).to eq(promise) end end @@ -61,7 +61,7 @@ module Cassandra statement = double('simple statement') expect(Statements::Simple).to receive(:new).once.with(cql, [1], [:int], false).and_return(statement) - expect(client).to receive(:query).once.with(statement, session_options.override(arguments: [1], type_hints: [:int])).and_return(promise) + expect(statement).to receive(:accept).once.with(client, session_options.override(arguments: [1], type_hints: [:int])).and_return(promise) expect(session.execute_async(cql, arguments: [1], type_hints: [:int])).to eq(promise) end end @@ -75,7 +75,7 @@ module Cassandra statement = double('simple statement') expect(Statements::Simple).to receive(:new).once.with(cql, EMPTY_LIST, EMPTY_LIST, false).and_return(statement) - expect(client).to receive(:query).once.with(statement, session_options.override(options)).and_return(promise) + expect(statement).to receive(:accept).once.with(client, session_options.override(options)).and_return(promise) expect(session.execute_async(cql, options)).to eq(promise) end end From 6c9c5c247b73dd5a4215a304ef0505ec8f211243 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Fri, 22 Apr 2016 14:44:14 -0700 Subject: [PATCH 041/196] Updated docs.yaml, replacing 2.1.5 with 2.1.6 version --- docs.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs.yaml b/docs.yaml index 1b48aff89..20139ff87 100644 --- a/docs.yaml +++ b/docs.yaml @@ -39,8 +39,8 @@ links: versions: - name: v3.0.0.rc.2 ref: 24b4341b90fe412e2e10504e0adf9d0300fccecb - - name: v2.1.5 - ref: bbb7084d11be701ff42f298373c0255b9a3e2b7f + - name: v2.1.6 + ref: 474568501e5ef771a496c3a901f3cbdc3854f512 - name: v2.0.1 ref: 80cab0ad188511ec16eb39111c0b67329c754729 - name: v1.2.0 From e2cc9c7e9b355d5760b9eba57aa34f40a6731981 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Sat, 23 Apr 2016 09:22:27 -0700 Subject: [PATCH 042/196] RUBY-175 column names should be quoted when they are reserved words in table cql * Added quoting logic in Table, MaterializedView, and Index * Added unit tests for Table, MaterializedView, and Index --- lib/cassandra/index.rb | 26 ++-- lib/cassandra/table.rb | 10 +- lib/cassandra/util.rb | 132 +++++++++++++++++- .../cluster/schema/fetchers/1.2.19-schema.cql | 54 +++---- .../cluster/schema/fetchers/2.0.16-schema.cql | 36 ++--- .../cluster/schema/fetchers/2.1.9-schema.cql | 32 ++--- .../cluster/schema/fetchers/2.2.1-schema.cql | 54 +++---- .../cluster/schema/fetchers/3.0.0-schema.cql | 32 ++--- spec/cassandra/index_spec.rb | 72 ++++++++++ spec/cassandra/materialized_view_spec.rb | 111 +++++++++++++++ spec/cassandra/table_spec.rb | 100 +++++++++++++ 11 files changed, 541 insertions(+), 118 deletions(-) create mode 100644 spec/cassandra/index_spec.rb create mode 100644 spec/cassandra/materialized_view_spec.rb create mode 100644 spec/cassandra/table_spec.rb diff --git a/lib/cassandra/index.rb b/lib/cassandra/index.rb index 900576afe..8e6d964ce 100644 --- a/lib/cassandra/index.rb +++ b/lib/cassandra/index.rb @@ -39,8 +39,17 @@ def initialize(table, @table = table @name = name.freeze @kind = kind - @target = target.freeze @options = options.freeze + + # Target is a bit tricky; it may be an escaped name or not + # depending on C* version. Unify to be unescaped since a user + # who wants to know the target would want the bare column name. + + @target = if target[0] == '"' + target[1..-2] + else + target + end.freeze end # @return [Boolean] whether or not this index uses a custom class. @@ -57,13 +66,14 @@ def custom_class_name def to_cql keyspace_name = Util.escape_name(@table.keyspace.name) table_name = Util.escape_name(@table.name) - index_name = Util.escape_name(name) + index_name = Util.escape_name(@name) + column_name = Util.escape_name(@target) if custom_index? - "CREATE CUSTOM INDEX #{index_name} ON #{keyspace_name}.#{table_name} (#{target}) " \ - "USING '#{@options['class_name']}' #{options_cql};" + "CREATE CUSTOM INDEX #{index_name} ON #{keyspace_name}.#{table_name} (#{column_name}) " \ + "USING '#{@options['class_name']}'#{options_cql};" else - "CREATE INDEX #{index_name} ON #{keyspace_name}.#{table_name} (#{target});" + "CREATE INDEX #{index_name} ON #{keyspace_name}.#{table_name} (#{column_name});" end end @@ -81,7 +91,7 @@ def eql?(other) # @private def inspect "#<#{self.class.name}:0x#{object_id.to_s(16)} " \ - "@name=#{@name} table_name=#{@table.name} kind=#{@kind} target=#{@target}>" + "@name=#{@name.inspect} @table=#{@table.inspect} @kind=#{@kind.inspect} @target=#{@target.inspect}>" end private @@ -93,9 +103,9 @@ def options_cql end return '' if filtered_options.empty? - result = 'WITH OPTIONS = {' + result = ' WITH OPTIONS = {' result << filtered_options.map do |key, value| - "'#{key}':'#{value}'" + "'#{key}': '#{value}'" end.join(', ') result << '}' result diff --git a/lib/cassandra/table.rb b/lib/cassandra/table.rb index 3fabc8fdf..8f9f5e592 100644 --- a/lib/cassandra/table.rb +++ b/lib/cassandra/table.rb @@ -80,14 +80,14 @@ def to_cql else cql << ",\n" end - cql << " #{column.name} #{type_to_cql(column.type, column.frozen?)}" + cql << " #{Util.escape_name(column.name)} #{type_to_cql(column.type, column.frozen?)}" cql << ' PRIMARY KEY' if primary_key && column.name == primary_key end unless primary_key cql << ",\n PRIMARY KEY (" if @partition_key.one? - cql << @partition_key.first.name + cql << Util.escape_name(@partition_key.first.name) else cql << '(' first = true @@ -97,12 +97,12 @@ def to_cql else cql << ', ' end - cql << column.name + cql << Util.escape_name(column.name) end cql << ')' end @clustering_columns.each do |column| - cql << ", #{column.name}" + cql << ", #{Util.escape_name(column.name)}" end cql << ')' end @@ -118,7 +118,7 @@ def to_cql else cql << ', ' end - cql << "#{column.name} #{order.to_s.upcase}" + cql << "#{Util.escape_name(column.name)} #{order.to_s.upcase}" end cql << ")\n AND " end diff --git a/lib/cassandra/util.rb b/lib/cassandra/util.rb index 687a51ab8..929a11428 100644 --- a/lib/cassandra/util.rb +++ b/lib/cassandra/util.rb @@ -162,7 +162,9 @@ def encode_tuple(tuple, io = StringIO.new) end def escape_name(name) - return name if name[LOWERCASE_REGEXP] == name + # If name only contains lower-case chars and it's not a reserved word, return it + # as-is. Otherwise, quote. + return name if name[LOWERCASE_REGEXP] == name && !RESERVED_WORDS.include?(name) DBL_QUOT + name + DBL_QUOT end @@ -327,5 +329,133 @@ def assert_equal(expected, actual, message = nil, &block) PRN_OPN = '('.freeze # @private PRN_CLS = ')'.freeze + RESERVED_WORDS = Set.new(%w( + add + aggregate + all + allow + alter + and + apply + as + asc + ascii + authorize + batch + begin + bigint + blob + boolean + by + called + clustering + columnfamily + compact + contains + count + counter + create + custom + date + decimal + delete + desc + describe + distinct + double + drop + entries + execute + exists + filtering + finalfunc + float + from + frozen + full + function + functions + grant + if + in + index + inet + infinity + initcond + input + insert + int + into + is + json + key + keys + keyspace + keyspaces + language + limit + list + login + map + materialized + modify + nan + nologin + norecursive + nosuperuser + not + null + of + on + options + or + order + password + permission + permissions + primary + rename + replace + returns + revoke + role + roles + schema + select + set + sfunc + smallint + static + storage + stype + superuser + table + text + time + timestamp + timeuuid + tinyint + to + token + trigger + truncate + ttl + tuple + type + unlogged + update + use + user + users + using + uuid + values + varchar + varint + view + where + with + writetime + )).freeze end end diff --git a/spec/cassandra/cluster/schema/fetchers/1.2.19-schema.cql b/spec/cassandra/cluster/schema/fetchers/1.2.19-schema.cql index f435a80f9..c95c0c30c 100644 --- a/spec/cassandra/cluster/schema/fetchers/1.2.19-schema.cql +++ b/spec/cassandra/cluster/schema/fetchers/1.2.19-schema.cql @@ -1,10 +1,10 @@ CREATE KEYSPACE system WITH replication = {'class': 'LocalStrategy'} AND durable_writes = true; CREATE TABLE system."HintsColumnFamily" ( - key blob, - column1 blob, + "key" blob, + "column1" blob, value blob, - PRIMARY KEY (key, column1) + PRIMARY KEY ("key", "column1") ) WITH COMPACT STORAGE AND bloom_filter_fp_chance = 0.01 @@ -36,10 +36,10 @@ WITH COMPACT STORAGE AND replicate_on_write = 'true'; CREATE TABLE system."LocationInfo" ( - key blob, - column1 blob, + "key" blob, + "column1" blob, value blob, - PRIMARY KEY (key, column1) + PRIMARY KEY ("key", "column1") ) WITH COMPACT STORAGE AND bloom_filter_fp_chance = 0.01 @@ -54,10 +54,10 @@ WITH COMPACT STORAGE AND replicate_on_write = 'true'; CREATE TABLE system."Migrations" ( - key blob, - column1 timeuuid, + "key" blob, + "column1" timeuuid, value blob, - PRIMARY KEY (key, column1) + PRIMARY KEY ("key", "column1") ) WITH COMPACT STORAGE AND bloom_filter_fp_chance = 0.01 @@ -72,9 +72,9 @@ WITH COMPACT STORAGE AND replicate_on_write = 'true'; CREATE TABLE system."NodeIdInfo" ( - key text, + "key" text, id timeuuid, - PRIMARY KEY (key, id) + PRIMARY KEY ("key", id) ) WITH COMPACT STORAGE AND bloom_filter_fp_chance = 0.01 @@ -89,10 +89,10 @@ WITH COMPACT STORAGE AND replicate_on_write = 'true'; CREATE TABLE system."Schema" ( - key blob, - column1 text, + "key" blob, + "column1" text, value blob, - PRIMARY KEY (key, column1) + PRIMARY KEY ("key", "column1") ) WITH COMPACT STORAGE AND bloom_filter_fp_chance = 0.01 @@ -142,7 +142,7 @@ WITH COMPACT STORAGE AND replicate_on_write = 'true'; CREATE TABLE system.local ( - key text PRIMARY KEY, + "key" text PRIMARY KEY, bootstrapped text, cluster_name text, cql_version text, @@ -243,7 +243,7 @@ CREATE TABLE system.schema_columnfamilies ( read_repair_chance double, replicate_on_write boolean, subcomparator text, - type text, + "type" text, value_alias text, PRIMARY KEY (keyspace_name, columnfamily_name) ) @@ -342,10 +342,10 @@ WITH bloom_filter_fp_chance = 0.01 CREATE KEYSPACE simplex WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'} AND durable_writes = true; CREATE TABLE simplex."t1" ( - f1 int, - f2 int, - f3 int, - PRIMARY KEY (f1, f2) + "f1" int, + "f2" int, + "f3" int, + PRIMARY KEY ("f1", "f2") ) WITH bloom_filter_fp_chance = 0.01 AND caching = 'KEYS_ONLY' @@ -361,12 +361,12 @@ WITH bloom_filter_fp_chance = 0.01 CREATE INDEX "ind1" ON simplex."t1" ("f3"); CREATE TABLE simplex."t2" ( - f1 int, - f2 int, - f3 int, - PRIMARY KEY (f1, f2) + "f1" int, + "f2" int, + "f3" int, + PRIMARY KEY ("f1", "f2") ) -WITH CLUSTERING ORDER BY (f2 DESC) +WITH CLUSTERING ORDER BY ("f2" DESC) AND COMPACT STORAGE AND bloom_filter_fp_chance = 0.01 AND caching = 'KEYS_ONLY' @@ -380,8 +380,8 @@ WITH CLUSTERING ORDER BY (f2 DESC) AND replicate_on_write = 'true'; CREATE TABLE simplex."t3" ( - f1 int PRIMARY KEY, - f2 int + "f1" int PRIMARY KEY, + "f2" int ) WITH bloom_filter_fp_chance = 0.01 AND caching = 'KEYS_ONLY' diff --git a/spec/cassandra/cluster/schema/fetchers/2.0.16-schema.cql b/spec/cassandra/cluster/schema/fetchers/2.0.16-schema.cql index 58628064a..51906a71e 100644 --- a/spec/cassandra/cluster/schema/fetchers/2.0.16-schema.cql +++ b/spec/cassandra/cluster/schema/fetchers/2.0.16-schema.cql @@ -22,9 +22,9 @@ WITH COMPACT STORAGE AND speculative_retry = '99.0PERCENTILE'; CREATE TABLE system."NodeIdInfo" ( - key text, + "key" text, id timeuuid, - PRIMARY KEY (key, id) + PRIMARY KEY ("key", id) ) WITH COMPACT STORAGE AND bloom_filter_fp_chance = 0.01 @@ -132,7 +132,7 @@ WITH COMPACT STORAGE AND speculative_retry = '99.0PERCENTILE'; CREATE TABLE system.local ( - key text PRIMARY KEY, + "key" text PRIMARY KEY, bootstrapped text, broadcast_address inet, cluster_name text, @@ -282,7 +282,7 @@ CREATE TABLE system.schema_columnfamilies ( replicate_on_write boolean, speculative_retry text, subcomparator text, - type text, + "type" text, value_alias text, PRIMARY KEY (keyspace_name, columnfamily_name) ) @@ -309,7 +309,7 @@ CREATE TABLE system.schema_columns ( index_name text, index_options text, index_type text, - type text, + "type" text, validator text, PRIMARY KEY (keyspace_name, columnfamily_name, column_name) ) @@ -376,8 +376,8 @@ CREATE TABLE system.sstable_activity ( keyspace_name text, columnfamily_name text, generation int, - rate_120m double, - rate_15m double, + "rate_120m" double, + "rate_15m" double, PRIMARY KEY ((keyspace_name, columnfamily_name, generation)) ) WITH bloom_filter_fp_chance = 0.01 @@ -447,10 +447,10 @@ WITH bloom_filter_fp_chance = 0.01 CREATE KEYSPACE simplex WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'} AND durable_writes = true; CREATE TABLE simplex."t1" ( - f1 int, - f2 int, - f3 int, - PRIMARY KEY (f1, f2) + "f1" int, + "f2" int, + "f3" int, + PRIMARY KEY ("f1", "f2") ) WITH bloom_filter_fp_chance = 0.01 AND caching = 'KEYS_ONLY' @@ -470,12 +470,12 @@ WITH bloom_filter_fp_chance = 0.01 CREATE INDEX "ind1" ON simplex."t1" ("f2"); CREATE TABLE simplex."t2" ( - f1 int, - f2 int, - f3 int, - PRIMARY KEY (f1, f2) + "f1" int, + "f2" int, + "f3" int, + PRIMARY KEY ("f1", "f2") ) -WITH CLUSTERING ORDER BY (f2 DESC) +WITH CLUSTERING ORDER BY ("f2" DESC) AND COMPACT STORAGE AND bloom_filter_fp_chance = 0.01 AND caching = 'KEYS_ONLY' @@ -493,8 +493,8 @@ WITH CLUSTERING ORDER BY (f2 DESC) AND speculative_retry = '99.0PERCENTILE'; CREATE TABLE simplex."t3" ( - f1 int PRIMARY KEY, - f2 int + "f1" int PRIMARY KEY, + "f2" int ) WITH bloom_filter_fp_chance = 0.01 AND caching = 'KEYS_ONLY' diff --git a/spec/cassandra/cluster/schema/fetchers/2.1.9-schema.cql b/spec/cassandra/cluster/schema/fetchers/2.1.9-schema.cql index c4907c6bc..f596a6458 100644 --- a/spec/cassandra/cluster/schema/fetchers/2.1.9-schema.cql +++ b/spec/cassandra/cluster/schema/fetchers/2.1.9-schema.cql @@ -106,7 +106,7 @@ WITH COMPACT STORAGE AND speculative_retry = '99.0PERCENTILE'; CREATE TABLE system.local ( - key text PRIMARY KEY, + "key" text PRIMARY KEY, bootstrapped text, broadcast_address inet, cluster_name text, @@ -253,7 +253,7 @@ CREATE TABLE system.schema_columnfamilies ( read_repair_chance double, speculative_retry text, subcomparator text, - type text, + "type" text, value_alias text, PRIMARY KEY (keyspace_name, columnfamily_name) ) @@ -279,7 +279,7 @@ CREATE TABLE system.schema_columns ( index_name text, index_options text, index_type text, - type text, + "type" text, validator text, PRIMARY KEY (keyspace_name, columnfamily_name, column_name) ) @@ -387,8 +387,8 @@ CREATE TABLE system.sstable_activity ( keyspace_name text, columnfamily_name text, generation int, - rate_120m double, - rate_15m double, + "rate_120m" double, + "rate_15m" double, PRIMARY KEY ((keyspace_name, columnfamily_name, generation)) ) WITH bloom_filter_fp_chance = 0.01 @@ -455,10 +455,10 @@ WITH bloom_filter_fp_chance = 0.01 CREATE KEYSPACE simplex WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'} AND durable_writes = true; CREATE TABLE simplex."t1" ( - f1 int, - f2 int, - f3 int, - PRIMARY KEY (f1, f2) + "f1" int, + "f2" int, + "f3" int, + PRIMARY KEY ("f1", "f2") ) WITH bloom_filter_fp_chance = 0.01 AND caching = '{"keys":"ALL", "rows_per_partition":"NONE"}' @@ -477,12 +477,12 @@ WITH bloom_filter_fp_chance = 0.01 CREATE INDEX "ind1" ON simplex."t1" ("f2"); CREATE TABLE simplex."t2" ( - f1 int, - f2 int, - f3 int, - PRIMARY KEY (f1, f2) + "f1" int, + "f2" int, + "f3" int, + PRIMARY KEY ("f1", "f2") ) -WITH CLUSTERING ORDER BY (f2 DESC) +WITH CLUSTERING ORDER BY ("f2" DESC) AND COMPACT STORAGE AND bloom_filter_fp_chance = 0.01 AND caching = '{"keys":"ALL", "rows_per_partition":"NONE"}' @@ -499,8 +499,8 @@ WITH CLUSTERING ORDER BY (f2 DESC) AND speculative_retry = '99.0PERCENTILE'; CREATE TABLE simplex."t3" ( - f1 int PRIMARY KEY, - f2 int + "f1" int PRIMARY KEY, + "f2" int ) WITH bloom_filter_fp_chance = 0.01 AND caching = '{"keys":"ALL", "rows_per_partition":"NONE"}' diff --git a/spec/cassandra/cluster/schema/fetchers/2.2.1-schema.cql b/spec/cassandra/cluster/schema/fetchers/2.2.1-schema.cql index 8d2262346..465e14fda 100644 --- a/spec/cassandra/cluster/schema/fetchers/2.2.1-schema.cql +++ b/spec/cassandra/cluster/schema/fetchers/2.2.1-schema.cql @@ -2,8 +2,8 @@ CREATE KEYSPACE system_auth WITH replication = {'class': 'SimpleStrategy', 'repl CREATE TABLE system_auth.resource_role_permissons_index ( resource text, - role text, - PRIMARY KEY (resource, role) + "role" text, + PRIMARY KEY (resource, "role") ) WITH bloom_filter_fp_chance = 0.01 AND caching = '{"keys":"ALL", "rows_per_partition":"NONE"}' @@ -20,9 +20,9 @@ WITH bloom_filter_fp_chance = 0.01 AND speculative_retry = '99.0PERCENTILE'; CREATE TABLE system_auth.role_members ( - role text, + "role" text, member text, - PRIMARY KEY (role, member) + PRIMARY KEY ("role", member) ) WITH bloom_filter_fp_chance = 0.01 AND caching = '{"keys":"ALL", "rows_per_partition":"NONE"}' @@ -39,10 +39,10 @@ WITH bloom_filter_fp_chance = 0.01 AND speculative_retry = '99.0PERCENTILE'; CREATE TABLE system_auth.role_permissions ( - role text, + "role" text, resource text, - permissions set, - PRIMARY KEY (role, resource) + "permissions" set, + PRIMARY KEY ("role", resource) ) WITH bloom_filter_fp_chance = 0.01 AND caching = '{"keys":"ALL", "rows_per_partition":"NONE"}' @@ -58,8 +58,8 @@ WITH bloom_filter_fp_chance = 0.01 AND read_repair_chance = 0.0 AND speculative_retry = '99.0PERCENTILE'; -CREATE TABLE system_auth.roles ( - role text PRIMARY KEY, +CREATE TABLE system_auth."roles" ( + "role" text PRIMARY KEY, can_login boolean, is_superuser boolean, member_of set, @@ -262,7 +262,7 @@ WITH COMPACT STORAGE AND speculative_retry = '99.0PERCENTILE'; CREATE TABLE system.local ( - key text PRIMARY KEY, + "key" text PRIMARY KEY, bootstrapped text, broadcast_address inet, cluster_name text, @@ -386,7 +386,7 @@ CREATE TABLE system.schema_aggregates ( signature frozen >, argument_types list, final_func text, - initcond blob, + "initcond" blob, return_type text, state_func text, state_type text, @@ -432,7 +432,7 @@ CREATE TABLE system.schema_columnfamilies ( read_repair_chance double, speculative_retry text, subcomparator text, - type text, + "type" text, PRIMARY KEY (keyspace_name, columnfamily_name) ) WITH bloom_filter_fp_chance = 0.01 @@ -457,7 +457,7 @@ CREATE TABLE system.schema_columns ( index_name text, index_options text, index_type text, - type text, + "type" text, validator text, PRIMARY KEY (keyspace_name, columnfamily_name, column_name) ) @@ -483,7 +483,7 @@ CREATE TABLE system.schema_functions ( argument_types list, body text, called_on_null_input boolean, - language text, + "language" text, return_type text, PRIMARY KEY (keyspace_name, function_name, signature) ) @@ -591,8 +591,8 @@ CREATE TABLE system.sstable_activity ( keyspace_name text, columnfamily_name text, generation int, - rate_120m double, - rate_15m double, + "rate_120m" double, + "rate_15m" double, PRIMARY KEY ((keyspace_name, columnfamily_name, generation)) ) WITH bloom_filter_fp_chance = 0.01 @@ -661,10 +661,10 @@ WITH bloom_filter_fp_chance = 0.01 CREATE KEYSPACE simplex WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'} AND durable_writes = true; CREATE TABLE simplex."t1" ( - f1 int, - f2 int, - f3 int, - PRIMARY KEY (f1, f2) + "f1" int, + "f2" int, + "f3" int, + PRIMARY KEY ("f1", "f2") ) WITH bloom_filter_fp_chance = 0.01 AND caching = '{"keys":"ALL", "rows_per_partition":"NONE"}' @@ -683,12 +683,12 @@ WITH bloom_filter_fp_chance = 0.01 CREATE INDEX "ind1" ON simplex."t1" ("f2"); CREATE TABLE simplex."t2" ( - f1 int, - f2 int, - f3 int, - PRIMARY KEY (f1, f2) + "f1" int, + "f2" int, + "f3" int, + PRIMARY KEY ("f1", "f2") ) -WITH CLUSTERING ORDER BY (f2 DESC) +WITH CLUSTERING ORDER BY ("f2" DESC) AND COMPACT STORAGE AND bloom_filter_fp_chance = 0.01 AND caching = '{"keys":"ALL", "rows_per_partition":"NONE"}' @@ -705,8 +705,8 @@ WITH CLUSTERING ORDER BY (f2 DESC) AND speculative_retry = '99.0PERCENTILE'; CREATE TABLE simplex."t3" ( - f1 int PRIMARY KEY, - f2 int + "f1" int PRIMARY KEY, + "f2" int ) WITH bloom_filter_fp_chance = 0.01 AND caching = '{"keys":"ALL", "rows_per_partition":"NONE"}' diff --git a/spec/cassandra/cluster/schema/fetchers/3.0.0-schema.cql b/spec/cassandra/cluster/schema/fetchers/3.0.0-schema.cql index 38e03d515..a0f3df5cc 100644 --- a/spec/cassandra/cluster/schema/fetchers/3.0.0-schema.cql +++ b/spec/cassandra/cluster/schema/fetchers/3.0.0-schema.cql @@ -1,10 +1,10 @@ CREATE KEYSPACE simplex WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'} AND durable_writes = true; CREATE TABLE simplex."t1" ( - f1 int, - f2 int, - f3 int, - PRIMARY KEY (f1, f2) + "f1" int, + "f2" int, + "f3" int, + PRIMARY KEY ("f1", "f2") ) WITH bloom_filter_fp_chance = 0.01 AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} @@ -21,13 +21,13 @@ WITH bloom_filter_fp_chance = 0.01 AND read_repair_chance = 0.0 AND speculative_retry = '99PERCENTILE'; -CREATE INDEX "ind1" ON simplex."t1" (f2); +CREATE INDEX "ind1" ON simplex."t1" ("f2"); CREATE TABLE simplex."t2" ( - f1 int, - f2 int, - f3 int, - PRIMARY KEY (f1, f2) + "f1" int, + "f2" int, + "f3" int, + PRIMARY KEY ("f1", "f2") ) WITH COMPACT STORAGE AND bloom_filter_fp_chance = 0.01 @@ -46,8 +46,8 @@ WITH COMPACT STORAGE AND speculative_retry = '99PERCENTILE'; CREATE TABLE simplex."t3" ( - f1 int PRIMARY KEY, - f2 int + "f1" int PRIMARY KEY, + "f2" int ) WITH COMPACT STORAGE AND bloom_filter_fp_chance = 0.01 @@ -66,12 +66,12 @@ WITH COMPACT STORAGE AND speculative_retry = '99PERCENTILE'; CREATE TABLE simplex."t4" ( - f1 int, - f2 int, - f3 int, - PRIMARY KEY (f1, f2) + "f1" int, + "f2" int, + "f3" int, + PRIMARY KEY ("f1", "f2") ) -WITH CLUSTERING ORDER BY (f2 DESC) +WITH CLUSTERING ORDER BY ("f2" DESC) AND COMPACT STORAGE AND bloom_filter_fp_chance = 0.01 AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} diff --git a/spec/cassandra/index_spec.rb b/spec/cassandra/index_spec.rb new file mode 100644 index 000000000..8e973c83e --- /dev/null +++ b/spec/cassandra/index_spec.rb @@ -0,0 +1,72 @@ +# encoding: utf-8 + +#-- +# Copyright 2013-2016 DataStax, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + +require 'spec_helper' + +module Cassandra + describe(Index) do + let(:table) { double('table') } + it 'should unescape target' do + ind = Cassandra::Index.new(table, 'myind', :composites, 'f1', {}) + expect(ind.target).to eq('f1') + + ind = Cassandra::Index.new(table, 'myind', :composites, '"f1"', {}) + expect(ind.target).to eq('f1') + end + + context :to_cql do + let(:ks) { double('keyspace') } + let(:col) { double('col') } + let(:col2) { double('col2') } + let(:col3) { double('from') } + let(:options) { {'target' => 'f1'} } + let(:id) { 1234 } + let(:where) { 'col=7' } + + before do + allow(ks).to receive(:name).and_return('myks1') + allow(table).to receive(:name).and_return('table1') + allow(table).to receive(:keyspace).and_return(ks) + end + + it 'should quote keyspace, view name, table name, columns properly for regular index' do + t = Cassandra::Index.new(table, 'myind1', :composites, 'f1', options) + expect(t.to_cql).to eq('CREATE INDEX "myind1" ON "myks1"."table1" ("f1");') + end + + it 'should quote keyspace, view name, table name, columns properly for custom index' do + options['opt1'] = 'val1' + options['class_name'] = 'com.datastax.Custom' + t = Cassandra::Index.new(table, 'myind1', :custom, 'f1', options) + expected = <<-EOF +CREATE CUSTOM INDEX "myind1" ON "myks1"."table1" ("f1") USING 'com.datastax.Custom' WITH OPTIONS = {'opt1': 'val1'}; +EOF + expect(t.to_cql).to eq(expected.chomp) + end + + it 'should exclude "WITH OPTIONS" if there are no options' do + options['class_name'] = 'com.datastax.Custom' + t = Cassandra::Index.new(table, 'myind1', :custom, 'f1', options) + expected = <<-EOF +CREATE CUSTOM INDEX "myind1" ON "myks1"."table1" ("f1") USING 'com.datastax.Custom'; + EOF + expect(t.to_cql).to eq(expected.chomp) + end + end + end +end diff --git a/spec/cassandra/materialized_view_spec.rb b/spec/cassandra/materialized_view_spec.rb new file mode 100644 index 000000000..f9e7347fb --- /dev/null +++ b/spec/cassandra/materialized_view_spec.rb @@ -0,0 +1,111 @@ +# encoding: utf-8 + +#-- +# Copyright 2013-2016 DataStax, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + +require 'spec_helper' + +include Cassandra::Types +module Cassandra + + describe(MaterializedView) do + context :to_cql do + let(:ks) { double('keyspace')} + let(:table) { double('table')} + let(:col) { double('col')} + let(:col2) { double('col2')} + let(:col3) { double('from')} + let(:options) { double('options')} + let(:id) { 1234 } + let(:where) { 'col=7'} + + before do + allow(ks).to receive(:name).and_return('myks1') + allow(table).to receive(:name).and_return('table1') + allow(col).to receive(:name).and_return('col') + allow(col).to receive(:type).and_return(Cassandra::Types.int) + allow(col2).to receive(:name).and_return('col2') + allow(col2).to receive(:type).and_return(int) + allow(col3).to receive(:name).and_return('from') + allow(col3).to receive(:type).and_return(varchar) + allow(options).to receive(:to_cql).and_return('opt1=value1') + end + + it 'should quote keyspace, view name, table name, columns properly' do + t = MaterializedView.new(ks, 'myview1', [col], [], [col2, col3], options, false, where, table,id) + expected_cql = <<-EOF +CREATE MATERIALIZED VIEW "myks1"."myview1" AS +SELECT col, "col2", "from" +FROM "myks1"."table1" +WHERE col=7 +PRIMARY KEY ((col)) +WITH opt1=value1; + EOF + expect(t.to_cql).to eq(expected_cql.chomp) + end + + it 'should quote primary key properly for simple partition key' do + t = MaterializedView.new(ks, 'myview1', [col], [col2], [col3], options, false, where, table,id) + expected_cql = <<-EOF +CREATE MATERIALIZED VIEW "myks1"."myview1" AS +SELECT col, "col2", "from" +FROM "myks1"."table1" +WHERE col=7 +PRIMARY KEY ((col), "col2") +WITH opt1=value1; + EOF + expect(t.to_cql).to eq(expected_cql.chomp) + end + + it 'should quote primary key properly for composite partition key' do + t = MaterializedView.new(ks, 'myview1', [col, col2], [col3], [], options, false, where, table,id) + expected_cql = <<-EOF +CREATE MATERIALIZED VIEW "myks1"."myview1" AS +SELECT col, "col2", "from" +FROM "myks1"."table1" +WHERE col=7 +PRIMARY KEY ((col, "col2"), "from") +WITH opt1=value1; + EOF + expect(t.to_cql).to eq(expected_cql.chomp) + end + + it 'should handle no where-clause properly' do + t = MaterializedView.new(ks, 'myview1', [col, col2], [col3], [], options, false, nil, table,id) + expected_cql = <<-EOF +CREATE MATERIALIZED VIEW "myks1"."myview1" AS +SELECT col, "col2", "from" +FROM "myks1"."table1" +PRIMARY KEY ((col, "col2"), "from") +WITH opt1=value1; + EOF + expect(t.to_cql).to eq(expected_cql.chomp) + end + + it 'should handle include-all-columns properly' do + t = MaterializedView.new(ks, 'myview1', [col, col2], [col3], [], options, true, nil, table,id) + expected_cql = <<-EOF +CREATE MATERIALIZED VIEW "myks1"."myview1" AS +SELECT * +FROM "myks1"."table1" +PRIMARY KEY ((col, "col2"), "from") +WITH opt1=value1; + EOF + expect(t.to_cql).to eq(expected_cql.chomp) + end + end + end +end diff --git a/spec/cassandra/table_spec.rb b/spec/cassandra/table_spec.rb new file mode 100644 index 000000000..bf05ecb0b --- /dev/null +++ b/spec/cassandra/table_spec.rb @@ -0,0 +1,100 @@ +# encoding: utf-8 + +#-- +# Copyright 2013-2016 DataStax, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + +require 'spec_helper' + +include Cassandra::Types +module Cassandra + + describe(Table) do + context :to_cql do + let(:ks) { double('keyspace')} + let(:col) { double('col')} + let(:col2) { double('col2')} + let(:col3) { double('from')} + let(:options) { double('options')} + let(:id) { 1234 } + before do + allow(ks).to receive(:name).and_return('myks1') + allow(col).to receive(:name).and_return('col') + allow(col).to receive(:type).and_return(Cassandra::Types.int) + allow(col2).to receive(:name).and_return('col2') + allow(col2).to receive(:type).and_return(int) + allow(col3).to receive(:name).and_return('from') + allow(col3).to receive(:type).and_return(varchar) + allow(options).to receive(:to_cql).and_return('opt1=value1') + end + + it 'should quote keyspace, table, columns properly' do + t = Table.new(ks, 'mytable1', [col], [], [col2, col3], options, [], id) + expected_cql = <<-EOF +CREATE TABLE "myks1"."mytable1" ( + col int PRIMARY KEY, + "col2" int, + "from" text +) +WITH opt1=value1; + EOF + expect(t.to_cql).to eq(expected_cql.chomp) + end + + it 'should quote primary key properly for simple partition key' do + t = Table.new(ks, 'mytable1', [col], [col2], [col3], options, [], id) + expected_cql = <<-EOF +CREATE TABLE "myks1"."mytable1" ( + col int, + "col2" int, + "from" text, + PRIMARY KEY (col, "col2") +) +WITH opt1=value1; + EOF + expect(t.to_cql).to eq(expected_cql.chomp) + end + + it 'should quote primary key properly for composite partition key' do + t = Table.new(ks, 'mytable1', [col, col2], [col3], [], options, [], id) + expected_cql = <<-EOF +CREATE TABLE "myks1"."mytable1" ( + col int, + "col2" int, + "from" text, + PRIMARY KEY ((col, "col2"), "from") +) +WITH opt1=value1; + EOF + expect(t.to_cql).to eq(expected_cql.chomp) + end + + it 'should handle clustering order properly' do + t = Table.new(ks, 'mytable1', [col, col2], [col3], [], options, [:desc], id) + expected_cql = <<-EOF +CREATE TABLE "myks1"."mytable1" ( + col int, + "col2" int, + "from" text, + PRIMARY KEY ((col, "col2"), "from") +) +WITH CLUSTERING ORDER BY ("from" DESC) + AND opt1=value1; + EOF + expect(t.to_cql).to eq(expected_cql.chomp) + end + end + end +end From 19279946136b1141c54bed7487f78c568e6e9e16 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 25 Apr 2016 14:47:53 -0700 Subject: [PATCH 043/196] RUBY-189 - Handle race condition where a host goes down at the same time as a query tries to execute on it. --- lib/cassandra/cluster/client.rb | 43 ++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/lib/cassandra/cluster/client.rb b/lib/cassandra/cluster/client.rb index 3251b0a53..6bf1febe9 100644 --- a/lib/cassandra/cluster/client.rb +++ b/lib/cassandra/cluster/client.rb @@ -644,7 +644,14 @@ def prepare_and_send_request_by_plan(host, errors, hosts) cql = statement.cql - id = synchronize { @prepared_statements[host][cql] } + + id, host_is_up = synchronize { + if @prepared_statements[host].nil? + [nil, false] + else + [@prepared_statements[host][cql], true] + end + } if id request.id = id @@ -659,6 +666,19 @@ def prepare_and_send_request_by_plan(host, timeout, errors, hosts) + elsif !host_is_up + # We've hit a race condition where the plan says we can query this host, but the host has gone + # down in the mean time. Just execute the plan again on the next host. + @logger.debug("#{host} is down; executing plan on next host") + execute_by_plan(promise, + keyspace, + statement, + options, + request, + plan, + timeout, + errors, + hosts) else prepare = prepare_statement(host, connection, cql, timeout) prepare.on_complete do |_| @@ -816,10 +836,27 @@ def batch_and_send_request_by_plan(host, cql = statement.cql if statement.is_a?(Statements::Bound) - id = synchronize { @prepared_statements[host][cql] } + id, host_is_up = synchronize { + if @prepared_statements[host].nil? + [nil, false] + else + [@prepared_statements[host][cql], true] + end + } if id request.add_prepared(id, statement.params, statement.params_types) + elsif !host_is_up + @logger.debug("#{host} is down; executing on next host in plan") + return batch_by_plan(promise, + keyspace, + statement, + options, + request, + plan, + timeout, + errors, + hosts) else unprepared[cql] << statement end @@ -1156,7 +1193,7 @@ def handle_response(response_future, end when Protocol::SetKeyspaceResultResponse @keyspace = r.keyspace - promise.fulfill(Results::Void.new(r.custom_payload, + promise.fulfill(Cassandra::Results::Void.new(r.custom_payload, r.warnings, r.trace_id, keyspace, From bb0f8e3d04f7355a35901f5edb6784269484a994 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 25 Apr 2016 15:08:18 -0700 Subject: [PATCH 044/196] RUBY-189 - Handle race condition where a host goes down at the same time as a query tries to execute on it. * Tweak the fix to be more efficient. --- lib/cassandra/cluster/client.rb | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/cassandra/cluster/client.rb b/lib/cassandra/cluster/client.rb index 6bf1febe9..f3b778bb9 100644 --- a/lib/cassandra/cluster/client.rb +++ b/lib/cassandra/cluster/client.rb @@ -644,12 +644,13 @@ def prepare_and_send_request_by_plan(host, errors, hosts) cql = statement.cql - - id, host_is_up = synchronize { + id = nil + host_is_up = true + synchronize { if @prepared_statements[host].nil? - [nil, false] + host_is_up = false else - [@prepared_statements[host][cql], true] + id = @prepared_statements[host][cql] end } @@ -836,11 +837,13 @@ def batch_and_send_request_by_plan(host, cql = statement.cql if statement.is_a?(Statements::Bound) - id, host_is_up = synchronize { + host_is_up = true + id = nil + synchronize { if @prepared_statements[host].nil? - [nil, false] + host_is_up = false else - [@prepared_statements[host][cql], true] + id = @prepared_statements[host][cql] end } From 75d48a99a646d499a70386d78793cde8ea3266da Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 25 Apr 2016 15:48:47 -0700 Subject: [PATCH 045/196] RUBY-175 * Fix broken integration tests. --- integration/indexes/materialized_view_test.rb | 4 ++-- integration/metadata_test.rb | 22 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/integration/indexes/materialized_view_test.rb b/integration/indexes/materialized_view_test.rb index ef5bd55cf..27fcc61bf 100644 --- a/integration/indexes/materialized_view_test.rb +++ b/integration/indexes/materialized_view_test.rb @@ -152,10 +152,10 @@ def test_column_ordering_is_deterministic assert @cluster.keyspace('simplex').has_materialized_view?('monthlyhigh') mv_cql = Regexp.new(/CREATE MATERIALIZED VIEW simplex.monthlyhigh AS -SELECT game, year, month, score, user, day +SELECT game, year, month, score, "user", day FROM simplex.scores WHERE game IS NOT NULL AND year IS NOT NULL AND month IS NOT NULL AND score IS NOT NULL AND user IS NOT NULL AND day IS NOT NULL -PRIMARY KEY \(\(game, year, month\), score, user, day\) +PRIMARY KEY \(\(game, year, month\), score, "user", day\) WITH bloom_filter_fp_chance = 0.01 AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} AND comment = '' diff --git a/integration/metadata_test.rb b/integration/metadata_test.rb index 2cf28bd3c..2375f2434 100644 --- a/integration/metadata_test.rb +++ b/integration/metadata_test.rb @@ -116,7 +116,7 @@ def test_can_retrieve_table_metadata def test_column_ordering_is_deterministic assert @cluster.keyspace('simplex').has_table?('users') table_meta = @cluster.keyspace('simplex').table('users') - table_cql = Regexp.new(/CREATE TABLE simplex\.users \( + table_cql = Regexp.new(/CREATE TABLE simplex\."users" \( user_id bigint, last text, age int, @@ -149,9 +149,9 @@ def test_skip_internal_columns_for_static_compact_table assert @cluster.keyspace('simplex').has_table?('blobby') table_meta = @cluster.keyspace('simplex').table('blobby') table_cql = Regexp.new(/CREATE TABLE simplex\.blobby \( - key blob PRIMARY KEY, - f1 blob, - f2 blob + "key" blob PRIMARY KEY, + "f1" blob, + "f2" blob \)/) assert_equal 0, table_meta.to_cql =~ table_cql, "actual cql: #{table_meta.to_cql}" @@ -184,10 +184,10 @@ def test_skip_internal_columns_for_dense_table assert @cluster.keyspace('simplex').has_table?('dense') table_meta = @cluster.keyspace('simplex').table('dense') table_cql = Regexp.new(/CREATE TABLE simplex\.dense \( - f1 int, - f2 int, - f3 int, - PRIMARY KEY \(f1, f2\) + "f1" int, + "f2" int, + "f3" int, + PRIMARY KEY \("f1", "f2"\) \) WITH COMPACT STORAGE/) @@ -217,9 +217,9 @@ def test_custom_type_column_in_table assert @cluster.keyspace('simplex').has_table?('custom') table_meta = @cluster.keyspace('simplex').table('custom') - table_cql = Regexp.new(/CREATE TABLE simplex\.custom \( - f1 int PRIMARY KEY, - f2 'org.apache.cassandra.db.marshal.CompositeType\(org.apache.cassandra.db.marshal.UUIDType,org.apache.cassandra.db.marshal.UTF8Type\)' + table_cql = Regexp.new(/CREATE TABLE simplex\."custom" \( + "f1" int PRIMARY KEY, + "f2" 'org.apache.cassandra.db.marshal.CompositeType\(org.apache.cassandra.db.marshal.UUIDType,org.apache.cassandra.db.marshal.UTF8Type\)' \)/) assert_equal 0, table_meta.to_cql =~ table_cql, "actual cql: #{table_meta.to_cql}" From e6e38fe0d0485d18b99b7862353d2a2ac327083b Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 25 Apr 2016 18:44:32 -0700 Subject: [PATCH 046/196] RUBY-189 * Added unit tests * Fixed a bug in batch retries when host goes down after plan is queried. --- lib/cassandra/cluster/client.rb | 2 +- spec/cassandra/cluster/client_spec.rb | 71 +++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/lib/cassandra/cluster/client.rb b/lib/cassandra/cluster/client.rb index f3b778bb9..765af5380 100644 --- a/lib/cassandra/cluster/client.rb +++ b/lib/cassandra/cluster/client.rb @@ -853,7 +853,7 @@ def batch_and_send_request_by_plan(host, @logger.debug("#{host} is down; executing on next host in plan") return batch_by_plan(promise, keyspace, - statement, + batch_statement, options, request, plan, diff --git a/spec/cassandra/cluster/client_spec.rb b/spec/cassandra/cluster/client_spec.rb index 1823cd03f..bf6639c56 100644 --- a/spec/cassandra/cluster/client_spec.rb +++ b/spec/cassandra/cluster/client_spec.rb @@ -642,6 +642,39 @@ class Cluster expect(count).to eq(2) end + it 'RUBY-189 - handles node down after prepare' do + promise = double('promise') + plan = double('plan') + connection = double('connection') + options = {} + errors = {} + hosts = [] + statement = double('statement') + expect(promise).to_not receive(:break) + expect(statement).to receive(:cql).and_return('select * from foo') + expect(client).to receive(:execute_by_plan).with(promise, + 'keyspace', + statement, + options, + 'request', + plan, + 12, + errors, + hosts) + client.send(:prepare_and_send_request_by_plan, + 'down_host', + connection, + promise, + 'keyspace', + statement, + options, + 'request', + plan, + 12, + errors, + hosts) + end + it 're-prepares a statement on unprepared error' do count = 0 error = true @@ -909,6 +942,44 @@ class Cluster expect(attempts).to eq(hosts) end + it 'RUBY-189 - handles node down after prepare in batch' do + promise = double('promise') + plan = double('plan') + connection = double('connection') + options = {} + errors = {} + hosts = [] + request = double('request') + batch_statement = double('statement') + bound_statement = double('bound_statement') + expect(request).to receive(:clear) + expect(bound_statement).to receive(:is_a?).and_return(true) + expect(bound_statement).to receive(:cql).and_return('select * from foo') + expect(batch_statement).to receive(:statements).and_return([bound_statement]) + expect(promise).to_not receive(:break) + expect(client).to receive(:batch_by_plan).with(promise, + 'keyspace', + batch_statement, + options, + request, + plan, + 12, + errors, + hosts) + client.send(:batch_and_send_request_by_plan, + 'down_host', + connection, + promise, + 'keyspace', + batch_statement, + request, + options, + plan, + 12, + errors, + hosts) + end + it 'raises if all hosts failed' do io_reactor.on_connection do |connection| connection.handle_request do |request| From be8a5cb56b0eb13b5a5fac2aae7f6882a541118b Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Tue, 26 Apr 2016 08:44:15 -0700 Subject: [PATCH 047/196] RUBY-175 * Fixed broken cucumber test * Fixed bug in index cql, where target was erroneously being quoted when the target was a function call (e.g. keys(foo)). --- features/basics/schema_metadata.feature | 2 +- lib/cassandra/index.rb | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/features/basics/schema_metadata.feature b/features/basics/schema_metadata.feature index 897d99422..d439a010d 100644 --- a/features/basics/schema_metadata.feature +++ b/features/basics/schema_metadata.feature @@ -71,7 +71,7 @@ Feature: Schema Metadata Clustering order: asc Num columns: 4 - CREATE TABLE simplex.users ( + CREATE TABLE simplex."users" ( user_id bigint, last text, age int, diff --git a/lib/cassandra/index.rb b/lib/cassandra/index.rb index 8e6d964ce..db700e6d7 100644 --- a/lib/cassandra/index.rb +++ b/lib/cassandra/index.rb @@ -67,13 +67,17 @@ def to_cql keyspace_name = Util.escape_name(@table.keyspace.name) table_name = Util.escape_name(@table.name) index_name = Util.escape_name(@name) - column_name = Util.escape_name(@target) + + # Target is interesting in that it's not necessarily a column name, + # so we can't simply escape it. If it contains a paren, we take it as is, + # otherwise assume it's a column name and escape accordingly. + escaped_target = @target.include?('(') ? @target : Util.escape_name(@target) if custom_index? - "CREATE CUSTOM INDEX #{index_name} ON #{keyspace_name}.#{table_name} (#{column_name}) " \ + "CREATE CUSTOM INDEX #{index_name} ON #{keyspace_name}.#{table_name} (#{escaped_target}) " \ "USING '#{@options['class_name']}'#{options_cql};" else - "CREATE INDEX #{index_name} ON #{keyspace_name}.#{table_name} (#{column_name});" + "CREATE INDEX #{index_name} ON #{keyspace_name}.#{table_name} (#{escaped_target});" end end From 6b24bb6615fdbd9811b1a7ed9cfc185543b89539 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Tue, 26 Apr 2016 09:53:15 -0700 Subject: [PATCH 048/196] RUBY-189 * Added regression tests. --- spec/cassandra/cluster/client_spec.rb | 71 ---------------- spec/regressions/RUBY-189_spec.rb | 116 ++++++++++++++++++++++++++ 2 files changed, 116 insertions(+), 71 deletions(-) create mode 100644 spec/regressions/RUBY-189_spec.rb diff --git a/spec/cassandra/cluster/client_spec.rb b/spec/cassandra/cluster/client_spec.rb index bf6639c56..1823cd03f 100644 --- a/spec/cassandra/cluster/client_spec.rb +++ b/spec/cassandra/cluster/client_spec.rb @@ -642,39 +642,6 @@ class Cluster expect(count).to eq(2) end - it 'RUBY-189 - handles node down after prepare' do - promise = double('promise') - plan = double('plan') - connection = double('connection') - options = {} - errors = {} - hosts = [] - statement = double('statement') - expect(promise).to_not receive(:break) - expect(statement).to receive(:cql).and_return('select * from foo') - expect(client).to receive(:execute_by_plan).with(promise, - 'keyspace', - statement, - options, - 'request', - plan, - 12, - errors, - hosts) - client.send(:prepare_and_send_request_by_plan, - 'down_host', - connection, - promise, - 'keyspace', - statement, - options, - 'request', - plan, - 12, - errors, - hosts) - end - it 're-prepares a statement on unprepared error' do count = 0 error = true @@ -942,44 +909,6 @@ class Cluster expect(attempts).to eq(hosts) end - it 'RUBY-189 - handles node down after prepare in batch' do - promise = double('promise') - plan = double('plan') - connection = double('connection') - options = {} - errors = {} - hosts = [] - request = double('request') - batch_statement = double('statement') - bound_statement = double('bound_statement') - expect(request).to receive(:clear) - expect(bound_statement).to receive(:is_a?).and_return(true) - expect(bound_statement).to receive(:cql).and_return('select * from foo') - expect(batch_statement).to receive(:statements).and_return([bound_statement]) - expect(promise).to_not receive(:break) - expect(client).to receive(:batch_by_plan).with(promise, - 'keyspace', - batch_statement, - options, - request, - plan, - 12, - errors, - hosts) - client.send(:batch_and_send_request_by_plan, - 'down_host', - connection, - promise, - 'keyspace', - batch_statement, - request, - options, - plan, - 12, - errors, - hosts) - end - it 'raises if all hosts failed' do io_reactor.on_connection do |connection| connection.handle_request do |request| diff --git a/spec/regressions/RUBY-189_spec.rb b/spec/regressions/RUBY-189_spec.rb new file mode 100644 index 000000000..420a688d5 --- /dev/null +++ b/spec/regressions/RUBY-189_spec.rb @@ -0,0 +1,116 @@ +# encoding: utf-8 + +#-- +# Copyright 2013-2016 DataStax, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + +require 'spec_helper' + +module Cassandra + class Cluster + describe(Client) do + let(:hosts) { ['127.0.0.1', '127.0.0.2'] } + let(:io_reactor) { FakeIoReactor.new } + let(:reconnection_policy) { Reconnection::Policies::Exponential.new(0.5, 30, 2) } + let(:load_balancing_policy) { FakeLoadBalancingPolicy.new(cluster_registry) } + let(:cluster_registry) { FakeClusterRegistry.new(hosts) } + let(:logger) { Cassandra::NullLogger.new } + let(:driver_settings) { { + :io_reactor => io_reactor, + :load_balancing_policy => load_balancing_policy, + :cluster_registry => cluster_registry, + :connections_per_local_node => 2, + :connections_per_remote_node => 1, + :reconnection_policy => reconnection_policy, + :executor => Executors::SameThread.new, + :logger => logger, + :protocol_version => 2 + } } + + let(:driver) { Driver.new(driver_settings) } + let(:client) { + Client.new(driver.logger, driver.cluster_registry, driver.cluster_schema, driver.io_reactor, driver.connector, + driver.load_balancing_policy, driver.reconnection_policy, driver.retry_policy, + driver.address_resolution_policy, driver.connection_options, driver.futures_factory) + } + + let(:promise) { double('promise') } + let(:plan) { double('plan') } + let(:connection) { double('connection') } + let(:options) { {} } + let(:errors) { {} } + let(:hosts) { [] } + let(:statement) { double('statement') } + let(:request) { double('request') } + let(:batch_statement) { double('batch_statement') } + let(:bound_statement) { double('bound_statement') } + + it 'RUBY-189 - handles node down after prepare' do + expect(promise).to_not receive(:break) + expect(statement).to receive(:cql).and_return('select * from foo') + expect(client).to receive(:execute_by_plan).with(promise, + 'keyspace', + statement, + options, + 'request', + plan, + 12, + errors, + hosts) + client.send(:prepare_and_send_request_by_plan, + 'down_host', + connection, + promise, + 'keyspace', + statement, + options, + 'request', + plan, + 12, + errors, + hosts) + end + + it 'RUBY-189 - handles node down after prepare in batch' do + expect(request).to receive(:clear) + expect(bound_statement).to receive(:is_a?).and_return(true) + expect(bound_statement).to receive(:cql).and_return('select * from foo') + expect(batch_statement).to receive(:statements).and_return([bound_statement]) + expect(promise).to_not receive(:break) + expect(client).to receive(:batch_by_plan).with(promise, + 'keyspace', + batch_statement, + options, + request, + plan, + 12, + errors, + hosts) + client.send(:batch_and_send_request_by_plan, + 'down_host', + connection, + promise, + 'keyspace', + batch_statement, + request, + options, + plan, + 12, + errors, + hosts) + end + end + end +end From fd31d92c648c178fd352f8b2fae3be2b1595fed7 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Wed, 27 Apr 2016 17:25:14 -0700 Subject: [PATCH 049/196] RUBY-162 - Cluster.inspect and Session.inspect should show interesting attributes of those objects * Reformatted code. --- lib/cassandra/cluster.rb | 5 +- lib/cassandra/cluster/client.rb | 28 ++-- lib/cassandra/session.rb | 4 +- lib/cassandra/util.rb | 254 ++++++++++++++++---------------- 4 files changed, 148 insertions(+), 143 deletions(-) diff --git a/lib/cassandra/cluster.rb b/lib/cassandra/cluster.rb index 03a80a07e..612815642 100644 --- a/lib/cassandra/cluster.rb +++ b/lib/cassandra/cluster.rb @@ -275,7 +275,10 @@ def close # @private def inspect - "#<#{self.class.name}:0x#{object_id.to_s(16)}>" + "#<#{self.class.name}:0x#{object_id.to_s(16)} " \ + "name=#{name.inspect}, " \ + "hosts=#{hosts.inspect}, " \ + "keyspaces=#{keyspaces.inspect}>" end end end diff --git a/lib/cassandra/cluster/client.rb b/lib/cassandra/cluster/client.rb index 765af5380..d8e8adfb8 100644 --- a/lib/cassandra/cluster/client.rb +++ b/lib/cassandra/cluster/client.rb @@ -646,13 +646,13 @@ def prepare_and_send_request_by_plan(host, cql = statement.cql id = nil host_is_up = true - synchronize { + synchronize do if @prepared_statements[host].nil? host_is_up = false else id = @prepared_statements[host][cql] end - } + end if id request.id = id @@ -839,13 +839,13 @@ def batch_and_send_request_by_plan(host, if statement.is_a?(Statements::Bound) host_is_up = true id = nil - synchronize { + synchronize do if @prepared_statements[host].nil? host_is_up = false else id = @prepared_statements[host][cql] end - } + end if id request.add_prepared(id, statement.params, statement.params_types) @@ -1197,16 +1197,16 @@ def handle_response(response_future, when Protocol::SetKeyspaceResultResponse @keyspace = r.keyspace promise.fulfill(Cassandra::Results::Void.new(r.custom_payload, - r.warnings, - r.trace_id, - keyspace, - statement, - options, - hosts, - request.consistency, - retries, - self, - @futures)) + r.warnings, + r.trace_id, + keyspace, + statement, + options, + hosts, + request.consistency, + retries, + self, + @futures)) when Protocol::PreparedResultResponse cql = request.cql synchronize do diff --git a/lib/cassandra/session.rb b/lib/cassandra/session.rb index cf9427de9..69d963dce 100644 --- a/lib/cassandra/session.rb +++ b/lib/cassandra/session.rb @@ -238,7 +238,9 @@ def close # @private def inspect - "#<#{self.class.name}:0x#{object_id.to_s(16)}>" + "#<#{self.class.name}:0x#{object_id.to_s(16)} " \ + "@keyspace=#{keyspace.inspect}, " \ + "@options=#{@options.inspect}>" end end end diff --git a/lib/cassandra/util.rb b/lib/cassandra/util.rb index 929a11428..66c855ddb 100644 --- a/lib/cassandra/util.rb +++ b/lib/cassandra/util.rb @@ -330,132 +330,132 @@ def assert_equal(expected, actual, message = nil, &block) # @private PRN_CLS = ')'.freeze RESERVED_WORDS = Set.new(%w( - add - aggregate - all - allow - alter - and - apply - as - asc - ascii - authorize - batch - begin - bigint - blob - boolean - by - called - clustering - columnfamily - compact - contains - count - counter - create - custom - date - decimal - delete - desc - describe - distinct - double - drop - entries - execute - exists - filtering - finalfunc - float - from - frozen - full - function - functions - grant - if - in - index - inet - infinity - initcond - input - insert - int - into - is - json - key - keys - keyspace - keyspaces - language - limit - list - login - map - materialized - modify - nan - nologin - norecursive - nosuperuser - not - null - of - on - options - or - order - password - permission - permissions - primary - rename - replace - returns - revoke - role - roles - schema - select - set - sfunc - smallint - static - storage - stype - superuser - table - text - time - timestamp - timeuuid - tinyint - to - token - trigger - truncate - ttl - tuple - type - unlogged - update - use - user - users - using - uuid - values - varchar - varint - view - where - with - writetime - )).freeze + add + aggregate + all + allow + alter + and + apply + as + asc + ascii + authorize + batch + begin + bigint + blob + boolean + by + called + clustering + columnfamily + compact + contains + count + counter + create + custom + date + decimal + delete + desc + describe + distinct + double + drop + entries + execute + exists + filtering + finalfunc + float + from + frozen + full + function + functions + grant + if + in + index + inet + infinity + initcond + input + insert + int + into + is + json + key + keys + keyspace + keyspaces + language + limit + list + login + map + materialized + modify + nan + nologin + norecursive + nosuperuser + not + null + of + on + options + or + order + password + permission + permissions + primary + rename + replace + returns + revoke + role + roles + schema + select + set + sfunc + smallint + static + storage + stype + superuser + table + text + time + timestamp + timeuuid + tinyint + to + token + trigger + truncate + ttl + tuple + type + unlogged + update + use + user + users + using + uuid + values + varchar + varint + view + where + with + writetime + )).freeze end end From dd81b24c22c777e0199ec1f84e7412a7a12b5de0 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Wed, 27 Apr 2016 17:39:42 -0700 Subject: [PATCH 050/196] RUBY-212 - increase default request timeout from 10 seconds to 12 seconds. --- CHANGELOG.md | 3 +++ lib/cassandra/driver.rb | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1d3a29ef..18d0ff0a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # master Features: +* Increased default request timeout (the `timeout` option to `Cassandra.cluster`), from 10 seconds to 12 seconds + because C* defaults to a 10 second timeout internally. The extra two seconds is buffer so that the client can + report the timeout in the server. This is also consistent with the Java driver. Bug Fixes: diff --git a/lib/cassandra/driver.rb b/lib/cassandra/driver.rb index aea712883..e2fc0f6af 100644 --- a/lib/cassandra/driver.rb +++ b/lib/cassandra/driver.rb @@ -165,7 +165,7 @@ def self.let(name, &block) let(:page_size) { 10000 } let(:heartbeat_interval) { 30 } let(:idle_timeout) { 60 } - let(:timeout) { 10 } + let(:timeout) { 12 } let(:synchronize_schema) { true } let(:schema_refresh_delay) { 1 } let(:schema_refresh_timeout) { 10 } From 62145cd51af0dd99d71d2f54b510134e123b6496 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Thu, 28 Apr 2016 17:50:00 -0700 Subject: [PATCH 051/196] RUBY-212 - increase default request timeout from 10 seconds to 12 seconds. * Increased timing tolerance in signal_spec to accommodate potentially slow VMs in CI. --- spec/cassandra/promise/signal_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/cassandra/promise/signal_spec.rb b/spec/cassandra/promise/signal_spec.rb index eb05c7a40..981447270 100644 --- a/spec/cassandra/promise/signal_spec.rb +++ b/spec/cassandra/promise/signal_spec.rb @@ -129,7 +129,7 @@ class Promise signal Thread.new { resolved = true; sleep(0.02); signal.success(value) } expect { signal.get(0) }.to raise_error(Errors::TimeoutError) - expect(signal.get(0.03)).to eq(value) + expect(signal.get(0.05)).to eq(value) expect(resolved).to be_truthy end end From ae99b12365454b8db4140b02da4be50ab5780326 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 2 May 2016 18:44:51 -0700 Subject: [PATCH 052/196] RUBY-214 - JRuby timestamps have millisecond precision and can cause client timestamp collisions. * Added configurable timestamp_generator option to Cassandra.cluster. * Implement TimeBasedGenerator and JRubyTimeBasedGenerator timestamp generator classes, and have the cluster default to the appropriate one. --- CHANGELOG.md | 56 ++++++++++-------- lib/cassandra.rb | 17 +++++- lib/cassandra/cluster.rb | 7 ++- lib/cassandra/cluster/client.rb | 10 ++-- lib/cassandra/driver.rb | 11 +++- lib/cassandra/protocol.rb | 2 + lib/cassandra/protocol/cql_byte_buffer.rb | 4 -- .../protocol/requests/batch_request.rb | 2 +- .../protocol/requests/execute_request.rb | 2 +- .../protocol/requests/query_request.rb | 2 +- lib/cassandra/protocol/timestamp.rb | 37 ++++++++++++ .../timestamp/jruby_time_based_generator.rb | 59 +++++++++++++++++++ .../timestamp/time_based_generator.rb | 38 ++++++++++++ spec/cassandra/cluster/client_spec.rb | 2 +- spec/cassandra/cluster_spec.rb | 2 +- spec/cassandra_spec.rb | 10 ++++ spec/regressions/RUBY-189_spec.rb | 2 +- 17 files changed, 220 insertions(+), 43 deletions(-) create mode 100644 lib/cassandra/protocol/timestamp.rb create mode 100644 lib/cassandra/protocol/timestamp/jruby_time_based_generator.rb create mode 100644 lib/cassandra/protocol/timestamp/time_based_generator.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index a1d3a29ef..7c62e7962 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,9 @@ # master Features: +* Add timestamp_generator cluster configuration option to allow user to specify his own generator for client timestamps. Bug Fixes: +* [RUBY-214](https://datastax-oss.atlassian.net/browse/RUBY-214) Client timestamps in JRuby are not fine-grained enough, causing timestamp collisions and lost rows in C*. # 3.0.0 rc2 Features: @@ -14,10 +16,10 @@ Features: * Make cluster configuration options list publicly available. (Thanks, Evan Prothro!) Bug Fixes: -* [RUBY-161] Protocol version negotiation in mixed version clusters should not fall back to v1 unless it is truly warranted. -* [RUBY-180] Column ordering is not deterministic in Table metadata. -* [RUBY-185] Internal columns in static-compact and dense tables should be ignored. -* [RUBY-186] Custom type column metadata should be parsed properly for C* 3.x schemas. +* [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. +* [RUBY-180](https://datastax-oss.atlassian.net/browse/RUBY-180) Column ordering is not deterministic in Table metadata. +* [RUBY-185](https://datastax-oss.atlassian.net/browse/RUBY-185) Internal columns in static-compact and dense tables should be ignored. +* [RUBY-186](https://datastax-oss.atlassian.net/browse/RUBY-186) Custom type column metadata should be parsed properly for C* 3.x schemas. # 3.0.0 rc1 @@ -26,9 +28,9 @@ Features: * Add Cassandra::Logger class to make it easy for users to enable debug logging in the client. Bug Fixes: -* [RUBY-154] Improve batch request performance, which had regressed in 3.0.0 beta1. -* [RUBY-155] Request timeout timer should not include request queuing time. -* [RUBY-156] Do not drop response frames that follow a frame containing a warning. +* [RUBY-154](https://datastax-oss.atlassian.net/browse/RUBY-154) Improve batch request performance, which had regressed in 3.0.0 beta1. +* [RUBY-155](https://datastax-oss.atlassian.net/browse/RUBY-155) Request timeout timer should not include request queuing time. +* [RUBY-156](https://datastax-oss.atlassian.net/browse/RUBY-156) Do not drop response frames that follow a frame containing a warning. # 3.0.0 beta1 @@ -48,10 +50,10 @@ Features: Bug Fixes: -* [RUBY-143] Retry querying system table for metadata of new hosts when prior attempts fail, ultimately enabling use of new hosts. -* [RUBY-150] Fixed a protocol decoding error that occurred when multiple messages are available in a stream. -* [RUBY-151] Decode incomplete UDTs properly. -* [RUBY-120] Tuples and UDTs can be used in sets and hash keys. +* [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. +* [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. +* [RUBY-151](https://datastax-oss.atlassian.net/browse/RUBY-151) Decode incomplete UDTs properly. +* [RUBY-120](https://datastax-oss.atlassian.net/browse/RUBY-120) Tuples and UDTs can be used in sets and hash keys. Breaking Changes: @@ -61,6 +63,12 @@ Breaking Changes: * Unavailable errors are retried on the next host in the load balancing plan by default. * Statement execution no longer retried on timeouts, unless `:idempotent => true` has been specified when executing. +# 2.1.6 +Bug Fixes: + +* [RUBY-202](https://datastax-oss.atlassian.net/browse/RUBY-202) Allow password authenticator to be used for LDAP authentication. This is actually a backport of + RUBY-169 for the 3.0.0 release. + # 2.1.5 Features: @@ -69,25 +77,25 @@ Features: Bug Fixes: -* [RUBY-128] Fix decoding of large values in maps, sets and lists. +* [RUBY-128](https://datastax-oss.atlassian.net/browse/RUBY-128) Fix decoding of large values in maps, sets and lists. # 2.1.4 Features: -* [RUBY-119] Use `require 'datastax/cassandra'` to avoid namespace conflicts -* [RUBY-90] Add support for disabling nagle algorithm (tcp nodelay), enabled by default. -* [RUBY-70] Add support for client-side timestamps, disabled by default. -* [RUBY-114] Add support for serial consistency in batch requests. +* [RUBY-119](https://datastax-oss.atlassian.net/browse/RUBY-119) Use `require 'datastax/cassandra'` to avoid namespace conflicts +* [RUBY-90](https://datastax-oss.atlassian.net/browse/RUBY-90) Add support for disabling nagle algorithm (tcp nodelay), enabled by default. +* [RUBY-70](https://datastax-oss.atlassian.net/browse/RUBY-70) Add support for client-side timestamps, disabled by default. +* [RUBY-114](https://datastax-oss.atlassian.net/browse/RUBY-114) Add support for serial consistency in batch requests. Bug Fixes: -* [RUBY-103] Don't regenerate schema metadata for the same replication +* [RUBY-103](https://datastax-oss.atlassian.net/browse/RUBY-103) Don't regenerate schema metadata for the same replication strategies and options -* [RUBY-102] Allow custom types in schema metadata -* [RUBY-97] Allow disabling of the initial population of schema metadata -* [RUBY-95] Speed up generation of large token maps -* [RUBY-116] fix thread leak on connection error +* [RUBY-102](https://datastax-oss.atlassian.net/browse/RUBY-102) Allow custom types in schema metadata +* [RUBY-97](https://datastax-oss.atlassian.net/browse/RUBY-97) Allow disabling of the initial population of schema metadata +* [RUBY-95](https://datastax-oss.atlassian.net/browse/RUBY-95) Speed up generation of large token maps +* [RUBY-116](https://datastax-oss.atlassian.net/browse/RUBY-116) fix thread leak on connection error Breaking Changes: @@ -106,7 +114,7 @@ Release removing accidental debug code from 2.1.1. Bug Fixes: -* [RUBY-98] Use of undefined class variable in `Table#create_partition_key` +* [RUBY-98](https://datastax-oss.atlassian.net/browse/RUBY-98) Use of undefined class variable in `Table#create_partition_key` # 2.1.0 @@ -124,13 +132,13 @@ Breaking Changes: Bug Fixes: -* [RUBY-93] Reconnection can overflow the stack +* [RUBY-93](https://datastax-oss.atlassian.net/browse/RUBY-93) Reconnection can overflow the stack # 2.0.1 Bug Fixes: -* [RUBY-87] Decoder corrupts incomplete response buffer +* [RUBY-87](https://datastax-oss.atlassian.net/browse/RUBY-87) Decoder corrupts incomplete response buffer # 2.0.0 diff --git a/lib/cassandra.rb b/lib/cassandra.rb index edf94a68c..70a84c32d 100644 --- a/lib/cassandra.rb +++ b/lib/cassandra.rb @@ -91,6 +91,7 @@ module Cassandra :ssl, :synchronize_schema, :timeout, + :timestamp_generator, :trace, :username ].freeze @@ -192,10 +193,15 @@ module Cassandra # # @option options [Boolean] :client_timestamps (false) whether the driver # should send timestamps for each executed statement. Enabling this setting - # allows mitigating Cassandra cluster clock skew because the timestamp of + # helps mitigate Cassandra cluster clock skew because the timestamp of # the client machine will be used. This does not help mitigate application # cluster clock skew. # + # @option options [Cassandra::Protocol::Timestamp::Generator] :timestamp_generator the timestamp generator to use for + # client timestamps (if :client_timestamps is true). Defaults to + # {Cassandra::Protocol::Timestamp::TimeBasedGenerator} for all Ruby flavors + # except JRuby. On JRuby, it defaults to {Cassandra::Protocol::Timestamp::JRubyTimeBasedGenerator}. + # # @option options [Boolean] :synchronize_schema (true) whether the driver # should automatically keep schema metadata synchronized. When enabled, the # driver updates schema metadata after receiving schema change @@ -684,6 +690,15 @@ def self.validate_and_massage_options(options) options[:synchronize_schema] = !!options[:synchronize_schema] if options.key?(:synchronize_schema) options[:client_timestamps] = !!options[:client_timestamps] if options.key?(:client_timestamps) + if options.key?(:timestamp_generator) + timestamp_generator = options[:timestamp_generator] + + Util.assert_responds_to(:next, timestamp_generator) do + ":timestamp_generator #{timestamp_generator.inspect} must respond to " \ + ":next, but doesn't" + end + end + if options.key?(:connections_per_local_node) connections_per_node = options[:connections_per_local_node] diff --git a/lib/cassandra/cluster.rb b/lib/cassandra/cluster.rb index 612815642..f99d7bd02 100644 --- a/lib/cassandra/cluster.rb +++ b/lib/cassandra/cluster.rb @@ -41,7 +41,8 @@ def initialize(logger, retry_policy, address_resolution_policy, connector, - futures_factory) + futures_factory, + timestamp_generator) @logger = logger @io_reactor = io_reactor @executor = executor @@ -57,6 +58,7 @@ def initialize(logger, @address_resolver = address_resolution_policy @connector = connector @futures = futures_factory + @timestamp_generator = timestamp_generator @control_connection.on_close do |_cause| begin @@ -204,7 +206,8 @@ def connect_async(keyspace = nil) @retry_policy, @address_resolver, @connection_options, - @futures) + @futures, + @timestamp_generator) session = Session.new(client, @execution_options, @futures) promise = @futures.promise diff --git a/lib/cassandra/cluster/client.rb b/lib/cassandra/cluster/client.rb index d8e8adfb8..4321746ff 100644 --- a/lib/cassandra/cluster/client.rb +++ b/lib/cassandra/cluster/client.rb @@ -34,7 +34,8 @@ def initialize(logger, retry_policy, address_resolution_policy, connection_options, - futures_factory) + futures_factory, + timestamp_generator) @logger = logger @registry = cluster_registry @schema = cluster_schema @@ -52,6 +53,7 @@ def initialize(logger, @pending_connections = ::Hash.new @keyspace = nil @state = :idle + @timestamp_generator = timestamp_generator mon_initialize end @@ -231,7 +233,7 @@ def query(statement, options) timestamp = nil if @connection_options.client_timestamps? && @connection_options.protocol_version > 2 - timestamp = ::Time.now + timestamp = @timestamp_generator.next end payload = nil payload = options.payload if @connection_options.protocol_version >= 4 @@ -289,7 +291,7 @@ def execute(statement, options) timestamp = nil if @connection_options.client_timestamps? && @connection_options.protocol_version > 2 - timestamp = ::Time.now + timestamp = @timestamp_generator.next end payload = nil payload = options.payload if @connection_options.protocol_version >= 4 @@ -327,7 +329,7 @@ def batch(statement, options) timestamp = nil if @connection_options.client_timestamps? && @connection_options.protocol_version > 2 - timestamp = ::Time.now + timestamp = @timestamp_generator.next end payload = nil payload = options.payload if @connection_options.protocol_version >= 4 diff --git a/lib/cassandra/driver.rb b/lib/cassandra/driver.rb index aea712883..7ec175eca 100644 --- a/lib/cassandra/driver.rb +++ b/lib/cassandra/driver.rb @@ -107,7 +107,8 @@ def self.let(name, &block) retry_policy, address_resolution_policy, connector, - futures_factory) + futures_factory, + timestamp_generator) end let(:execution_options) do @@ -173,7 +174,13 @@ def self.let(name, &block) let(:shuffle_replicas) { true } let(:client_timestamps) { false } let(:nodelay) { true } - + let(:timestamp_generator) do + if RUBY_ENGINE == 'jruby' + Cassandra::Protocol::Timestamp::JRubyTimeBasedGenerator.new + else + Cassandra::Protocol::Timestamp::TimeBasedGenerator.new + end + end let(:connections_per_local_node) { nil } let(:connections_per_remote_node) { nil } let(:requests_per_connection) { nil } diff --git a/lib/cassandra/protocol.rb b/lib/cassandra/protocol.rb index 819d7b3a2..1296627fe 100644 --- a/lib/cassandra/protocol.rb +++ b/lib/cassandra/protocol.rb @@ -84,6 +84,8 @@ module Constants require 'cassandra/protocol/requests/prepare_request' require 'cassandra/protocol/requests/execute_request' require 'cassandra/protocol/cql_protocol_handler' +require 'cassandra/protocol/timestamp/time_based_generator' +require 'cassandra/protocol/timestamp/jruby_time_based_generator' require 'cassandra/protocol/v1' require 'cassandra/protocol/v3' require 'cassandra/protocol/v4' diff --git a/lib/cassandra/protocol/cql_byte_buffer.rb b/lib/cassandra/protocol/cql_byte_buffer.rb index b4051e089..0e098d17d 100644 --- a/lib/cassandra/protocol/cql_byte_buffer.rb +++ b/lib/cassandra/protocol/cql_byte_buffer.rb @@ -324,10 +324,6 @@ def append_bytes_map(map) self end - def append_timestamp(timestamp) - append_long(timestamp.tv_sec * 1000000 + timestamp.tv_usec) - end - def append_long(n) top = n >> 32 bottom = n & 0xffffffff diff --git a/lib/cassandra/protocol/requests/batch_request.rb b/lib/cassandra/protocol/requests/batch_request.rb index 1cbc55646..3162c6a5f 100644 --- a/lib/cassandra/protocol/requests/batch_request.rb +++ b/lib/cassandra/protocol/requests/batch_request.rb @@ -81,7 +81,7 @@ def write(buffer, protocol_version, encoder) buffer.append(flags.chr) buffer.append_consistency(@serial_consistency) if @serial_consistency - buffer.append_timestamp(@timestamp) if @timestamp + buffer.append_long(@timestamp) if @timestamp end buffer diff --git a/lib/cassandra/protocol/requests/execute_request.rb b/lib/cassandra/protocol/requests/execute_request.rb index 39879582c..fa5e0c667 100644 --- a/lib/cassandra/protocol/requests/execute_request.rb +++ b/lib/cassandra/protocol/requests/execute_request.rb @@ -78,7 +78,7 @@ def write(buffer, protocol_version, encoder) buffer.append_int(@page_size) if @page_size buffer.append_bytes(@paging_state) if @paging_state buffer.append_consistency(@serial_consistency) if @serial_consistency - buffer.append_timestamp(@timestamp) if protocol_version > 2 && @timestamp + buffer.append_long(@timestamp) if protocol_version > 2 && @timestamp else encoder.write_parameters(buffer, @values, @metadata) buffer.append_consistency(@consistency) diff --git a/lib/cassandra/protocol/requests/query_request.rb b/lib/cassandra/protocol/requests/query_request.rb index 6ed0d34f1..b21f9dab2 100644 --- a/lib/cassandra/protocol/requests/query_request.rb +++ b/lib/cassandra/protocol/requests/query_request.rb @@ -71,7 +71,7 @@ def write(buffer, protocol_version, encoder) buffer.append_int(@page_size) if @page_size buffer.append_bytes(@paging_state) if @paging_state buffer.append_consistency(@serial_consistency) if @serial_consistency - buffer.append_timestamp(@timestamp) if protocol_version > 2 && @timestamp + buffer.append_long(@timestamp) if protocol_version > 2 && @timestamp end buffer end diff --git a/lib/cassandra/protocol/timestamp.rb b/lib/cassandra/protocol/timestamp.rb new file mode 100644 index 000000000..32d4c4a7d --- /dev/null +++ b/lib/cassandra/protocol/timestamp.rb @@ -0,0 +1,37 @@ +# encoding: utf-8 + +#-- +# Copyright 2013-2016 DataStax, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + +module Cassandra + module Protocol + module Timestamp + # A generator is used to create client-timestamps (in the form of long integers) to send with C* requests when + # the `:client_timestamps` cluster option is set to true. + # + # @abstract A timestamp generator given to {Cassandra.cluster} doesn't need to be + # a subclass of this class, but needs to implement the same methods. This + # class exists only for documentation purposes. + class Generator + # @!method next + # + # Create a new timestamp, as a 64-bit integer. Calls must return monotonically increasing values. + # + # @return [Integer] an integer representing a timestamp. + end + end + end +end diff --git a/lib/cassandra/protocol/timestamp/jruby_time_based_generator.rb b/lib/cassandra/protocol/timestamp/jruby_time_based_generator.rb new file mode 100644 index 000000000..7676509a5 --- /dev/null +++ b/lib/cassandra/protocol/timestamp/jruby_time_based_generator.rb @@ -0,0 +1,59 @@ +# encoding: utf-8 + +#-- +# Copyright 2013-2016 DataStax, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + +module Cassandra + module Protocol + module Timestamp + # In JRuby, {::Time} has millisecond precision. We require client timestamps to have microsecond precision to + # minimize clashes in C*. This generator keeps track of the last generated timestamp, and if the current-time + # is within the same millisecond as the last, it fills the microsecond portion of the new timestamp with the + # value of an incrementing counter. + # + # For example, if the generator triggers twice at time 12345678000 (microsecond granularity, but ms precisions + # as shown by 0's for the three least-significant digits), it'll return 12345678000 and 12345678001. + class JRubyTimeBasedGenerator + include MonitorMixin + + # @private + def initialize + mon_initialize + @last = 0 + end + + # Create a new timestamp, as a 64-bit integer. + # + # @return [Integer] an integer representing a timestamp. + def next + now = ::Time.now + now_millis = now.tv_sec * 1000 + now.tv_usec / 1000 + synchronize do + millis = @last / 1000 + counter = @last % 1000 + if millis >= now_millis + counter += 1 + else + millis = now_millis + counter = 0 + end + @last = millis * 1000 + counter + end + end + end + end + end +end diff --git a/lib/cassandra/protocol/timestamp/time_based_generator.rb b/lib/cassandra/protocol/timestamp/time_based_generator.rb new file mode 100644 index 000000000..9ecb5b4e1 --- /dev/null +++ b/lib/cassandra/protocol/timestamp/time_based_generator.rb @@ -0,0 +1,38 @@ +# encoding: utf-8 + +#-- +# Copyright 2013-2016 DataStax, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + +module Cassandra + module Protocol + module Timestamp + # Generate long integer timestamps from current time. This implementation relies on the {::Time} class to return + # microsecond precision time. + # @note It is not appropriate for use with JRuby because its {::Time#now} returns millisecond precision time. + class TimeBasedGenerator + # Create a new timestamp, as a 64-bit integer. This is just a wrapper around Time::now. + # + # @return [Integer] an integer representing a timestamp. + def next + # Use Time.now, which has microsecond precision on MRI (and probably Rubinius) to make an int representing + # client timestamp in protocol requests. + timestamp = ::Time.now + timestamp.tv_sec * 1000000 + timestamp.tv_usec + end + end + end + end +end diff --git a/spec/cassandra/cluster/client_spec.rb b/spec/cassandra/cluster/client_spec.rb index 1823cd03f..133b438c9 100644 --- a/spec/cassandra/cluster/client_spec.rb +++ b/spec/cassandra/cluster/client_spec.rb @@ -40,7 +40,7 @@ class Cluster } } let(:driver) { Driver.new(driver_settings) } - let(:client) { Client.new(driver.logger, driver.cluster_registry, driver.cluster_schema, driver.io_reactor, driver.connector, driver.load_balancing_policy, driver.reconnection_policy, driver.retry_policy, driver.address_resolution_policy, driver.connection_options, driver.futures_factory) } + let(:client) { Client.new(driver.logger, driver.cluster_registry, driver.cluster_schema, driver.io_reactor, driver.connector, driver.load_balancing_policy, driver.reconnection_policy, driver.retry_policy, driver.address_resolution_policy, driver.connection_options, driver.futures_factory, driver.timestamp_generator) } before do io_reactor.connection_options = driver.connection_options diff --git a/spec/cassandra/cluster_spec.rb b/spec/cassandra/cluster_spec.rb index e451e985e..9d63920fc 100644 --- a/spec/cassandra/cluster_spec.rb +++ b/spec/cassandra/cluster_spec.rb @@ -33,7 +33,7 @@ module Cassandra }) } - let(:cluster) { Cluster.new(driver.logger, io_reactor, executor, control_connection, cluster_registry, driver.cluster_schema, driver.cluster_metadata, driver.execution_options, driver.connection_options, load_balancing_policy, driver.reconnection_policy, driver.retry_policy, driver.address_resolution_policy, driver.connector, driver.futures_factory) } + let(:cluster) { Cluster.new(driver.logger, io_reactor, executor, control_connection, cluster_registry, driver.cluster_schema, driver.cluster_metadata, driver.execution_options, driver.connection_options, load_balancing_policy, driver.reconnection_policy, driver.retry_policy, driver.address_resolution_policy, driver.connector, driver.futures_factory, driver.timestamp_generator) } before do io_reactor.connection_options = driver.connection_options diff --git a/spec/cassandra_spec.rb b/spec/cassandra_spec.rb index 5b91e28fb..781096b6f 100644 --- a/spec/cassandra_spec.rb +++ b/spec/cassandra_spec.rb @@ -379,6 +379,16 @@ def resolve expect(C.validate(client_timestamps: 0)).to eq({client_timestamps: true}) end + it 'should validate :timestamp_generator' do + valid_generator = Object.new + def valid_generator.next + 42 + end + expect { C.validate(timestamp_generator: Object.new) }.to raise_error(ArgumentError) + expect(C.validate(timestamp_generator: valid_generator)). + to eq({ timestamp_generator: valid_generator }) + end + it 'should validate :connections_per_local_node' do expect { C.validate(connections_per_local_node: 'a') }.to raise_error(ArgumentError) expect { C.validate(connections_per_local_node: 0) }.to raise_error(ArgumentError) diff --git a/spec/regressions/RUBY-189_spec.rb b/spec/regressions/RUBY-189_spec.rb index 420a688d5..4915caf7e 100644 --- a/spec/regressions/RUBY-189_spec.rb +++ b/spec/regressions/RUBY-189_spec.rb @@ -43,7 +43,7 @@ class Cluster let(:client) { Client.new(driver.logger, driver.cluster_registry, driver.cluster_schema, driver.io_reactor, driver.connector, driver.load_balancing_policy, driver.reconnection_policy, driver.retry_policy, - driver.address_resolution_policy, driver.connection_options, driver.futures_factory) + driver.address_resolution_policy, driver.connection_options, driver.futures_factory, driver.timestamp_generator) } let(:promise) { double('promise') } From 6addf0da2b1588c14e4bb3d3144a871cf5984d6a Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Tue, 3 May 2016 10:31:31 -0700 Subject: [PATCH 053/196] RUBY-214 - JRuby timestamps have millisecond precision and can cause client timestamp collisions. * Reduce amount of time spent in lock when ms portion of time has changed. --- .../timestamp/jruby_time_based_generator.rb | 46 +++++++++++++++---- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/lib/cassandra/protocol/timestamp/jruby_time_based_generator.rb b/lib/cassandra/protocol/timestamp/jruby_time_based_generator.rb index 7676509a5..2d7b0f5be 100644 --- a/lib/cassandra/protocol/timestamp/jruby_time_based_generator.rb +++ b/lib/cassandra/protocol/timestamp/jruby_time_based_generator.rb @@ -41,17 +41,45 @@ def initialize def next now = ::Time.now now_millis = now.tv_sec * 1000 + now.tv_usec / 1000 - synchronize do - millis = @last / 1000 - counter = @last % 1000 - if millis >= now_millis - counter += 1 - else - millis = now_millis - counter = 0 + last = @last + last_millis = last / 1000 + if last_millis < now_millis + # Since milliseconds changed, we don't need to do the special counter logic to avoid collisions. + new_last = now_millis * 1000 + + synchronize do + if last == @last + # last didn't change beneath us while we did the above calculations, so we can set it with our new value. + @last = new_last + else + # Since last did change, it means our calculations above are now out-of-date and we need to + # recalculate and update. Now that we're under the lock, no one else can pull the rug out from + # under us and the calculation is stable. + update_last(now_millis) + end + return @last end - @last = millis * 1000 + counter end + + # If we get here, it means we're in the same millisecond as 'last', so we need to grab the lock, + # re-check that we're still in the same ms as 'last', and update last appropriately. + synchronize do + update_last(now_millis) + end + end + + private + # @private + def update_last(now_millis) + millis = @last / 1000 + counter = @last % 1000 + if millis >= now_millis + counter += 1 + else + millis = now_millis + counter = 0 + end + @last = millis * 1000 + counter end end end From 1305314269facfeea5dfc846bd019e1f69dbeacd Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Tue, 3 May 2016 10:34:58 -0700 Subject: [PATCH 054/196] RUBY-214 - JRuby timestamps have millisecond precision and can cause client timestamp collisions. * Updated 'next' method comments to be clear that the return value is in microseconds. --- lib/cassandra/protocol/timestamp/jruby_time_based_generator.rb | 2 +- lib/cassandra/protocol/timestamp/time_based_generator.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/cassandra/protocol/timestamp/jruby_time_based_generator.rb b/lib/cassandra/protocol/timestamp/jruby_time_based_generator.rb index 2d7b0f5be..941ccc240 100644 --- a/lib/cassandra/protocol/timestamp/jruby_time_based_generator.rb +++ b/lib/cassandra/protocol/timestamp/jruby_time_based_generator.rb @@ -37,7 +37,7 @@ def initialize # Create a new timestamp, as a 64-bit integer. # - # @return [Integer] an integer representing a timestamp. + # @return [Integer] an integer representing a timestamp in microseconds. def next now = ::Time.now now_millis = now.tv_sec * 1000 + now.tv_usec / 1000 diff --git a/lib/cassandra/protocol/timestamp/time_based_generator.rb b/lib/cassandra/protocol/timestamp/time_based_generator.rb index 9ecb5b4e1..ff6431f31 100644 --- a/lib/cassandra/protocol/timestamp/time_based_generator.rb +++ b/lib/cassandra/protocol/timestamp/time_based_generator.rb @@ -25,7 +25,7 @@ module Timestamp class TimeBasedGenerator # Create a new timestamp, as a 64-bit integer. This is just a wrapper around Time::now. # - # @return [Integer] an integer representing a timestamp. + # @return [Integer] an integer representing a timestamp in microseconds. def next # Use Time.now, which has microsecond precision on MRI (and probably Rubinius) to make an int representing # client timestamp in protocol requests. From aae40b652846f87eefccea429d556a57b25d5410 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Tue, 3 May 2016 10:40:25 -0700 Subject: [PATCH 055/196] RUBY-214 - JRuby timestamps have millisecond precision and can cause client timestamp collisions. * Reverted next impl because we can't actually avoid the lock in any case, and the code becomes unnecessarily complex when we try to minimize the time in lock. Performance metrics don't show any significant difference, so it's not worth it. --- .../timestamp/jruby_time_based_generator.rb | 46 ++++--------------- 1 file changed, 9 insertions(+), 37 deletions(-) diff --git a/lib/cassandra/protocol/timestamp/jruby_time_based_generator.rb b/lib/cassandra/protocol/timestamp/jruby_time_based_generator.rb index 941ccc240..c3d397b9d 100644 --- a/lib/cassandra/protocol/timestamp/jruby_time_based_generator.rb +++ b/lib/cassandra/protocol/timestamp/jruby_time_based_generator.rb @@ -41,45 +41,17 @@ def initialize def next now = ::Time.now now_millis = now.tv_sec * 1000 + now.tv_usec / 1000 - last = @last - last_millis = last / 1000 - if last_millis < now_millis - # Since milliseconds changed, we don't need to do the special counter logic to avoid collisions. - new_last = now_millis * 1000 - - synchronize do - if last == @last - # last didn't change beneath us while we did the above calculations, so we can set it with our new value. - @last = new_last - else - # Since last did change, it means our calculations above are now out-of-date and we need to - # recalculate and update. Now that we're under the lock, no one else can pull the rug out from - # under us and the calculation is stable. - update_last(now_millis) - end - return @last - end - end - - # If we get here, it means we're in the same millisecond as 'last', so we need to grab the lock, - # re-check that we're still in the same ms as 'last', and update last appropriately. synchronize do - update_last(now_millis) - end - end - - private - # @private - def update_last(now_millis) - millis = @last / 1000 - counter = @last % 1000 - if millis >= now_millis - counter += 1 - else - millis = now_millis - counter = 0 + millis = @last / 1000 + counter = @last % 1000 + if millis >= now_millis + counter += 1 + else + millis = now_millis + counter = 0 + end + @last = millis * 1000 + counter end - @last = millis * 1000 + counter end end end From a69f29c12725cf92e16d83908ffd4e9fb48af4a6 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Tue, 3 May 2016 11:41:33 -0700 Subject: [PATCH 056/196] RUBY-214 - JRuby timestamps have millisecond precision and can cause client timestamp collisions. * Various moving/renaming of classes to clarify their use. --- lib/cassandra.rb | 7 ++- lib/cassandra/driver.rb | 4 +- lib/cassandra/protocol.rb | 2 - lib/cassandra/protocol/timestamp.rb | 37 ------------ .../timestamp/jruby_time_based_generator.rb | 59 ------------------- lib/cassandra/timestamp_generator.rb | 35 +++++++++++ .../simple.rb} | 8 +-- .../ticking_on_duplicate_generator.rb | 58 ++++++++++++++++++ 8 files changed, 103 insertions(+), 107 deletions(-) delete mode 100644 lib/cassandra/protocol/timestamp.rb delete mode 100644 lib/cassandra/protocol/timestamp/jruby_time_based_generator.rb create mode 100644 lib/cassandra/timestamp_generator.rb rename lib/cassandra/{protocol/timestamp/time_based_generator.rb => timestamp_generator/simple.rb} (94%) create mode 100644 lib/cassandra/timestamp_generator/ticking_on_duplicate_generator.rb diff --git a/lib/cassandra.rb b/lib/cassandra.rb index 70a84c32d..ed2c29a4f 100644 --- a/lib/cassandra.rb +++ b/lib/cassandra.rb @@ -197,10 +197,10 @@ module Cassandra # the client machine will be used. This does not help mitigate application # cluster clock skew. # - # @option options [Cassandra::Protocol::Timestamp::Generator] :timestamp_generator the timestamp generator to use for + # @option options [Cassandra::TimestampGenerator] :timestamp_generator the timestamp generator to use for # client timestamps (if :client_timestamps is true). Defaults to - # {Cassandra::Protocol::Timestamp::TimeBasedGenerator} for all Ruby flavors - # except JRuby. On JRuby, it defaults to {Cassandra::Protocol::Timestamp::JRubyTimeBasedGenerator}. + # {Cassandra::TimestampGenerator::Simple} for all Ruby flavors + # except JRuby. On JRuby, it defaults to {Cassandra::TimestampGenerator::TickingOnDuplicate}. # # @option options [Boolean] :synchronize_schema (true) whether the driver # should automatically keep schema metadata synchronized. When enabled, the @@ -808,6 +808,7 @@ def self.validate_and_massage_options(options) require 'cassandra/reconnection' require 'cassandra/retry' require 'cassandra/address_resolution' +require 'cassandra/timestamp_generator' require 'cassandra/util' diff --git a/lib/cassandra/driver.rb b/lib/cassandra/driver.rb index 7ec175eca..ef4ba387a 100644 --- a/lib/cassandra/driver.rb +++ b/lib/cassandra/driver.rb @@ -176,9 +176,9 @@ def self.let(name, &block) let(:nodelay) { true } let(:timestamp_generator) do if RUBY_ENGINE == 'jruby' - Cassandra::Protocol::Timestamp::JRubyTimeBasedGenerator.new + Cassandra::TimestampGenerator::TickingOnDuplicate.new else - Cassandra::Protocol::Timestamp::TimeBasedGenerator.new + Cassandra::TimestampGenerator::Simple.new end end let(:connections_per_local_node) { nil } diff --git a/lib/cassandra/protocol.rb b/lib/cassandra/protocol.rb index 1296627fe..819d7b3a2 100644 --- a/lib/cassandra/protocol.rb +++ b/lib/cassandra/protocol.rb @@ -84,8 +84,6 @@ module Constants require 'cassandra/protocol/requests/prepare_request' require 'cassandra/protocol/requests/execute_request' require 'cassandra/protocol/cql_protocol_handler' -require 'cassandra/protocol/timestamp/time_based_generator' -require 'cassandra/protocol/timestamp/jruby_time_based_generator' require 'cassandra/protocol/v1' require 'cassandra/protocol/v3' require 'cassandra/protocol/v4' diff --git a/lib/cassandra/protocol/timestamp.rb b/lib/cassandra/protocol/timestamp.rb deleted file mode 100644 index 32d4c4a7d..000000000 --- a/lib/cassandra/protocol/timestamp.rb +++ /dev/null @@ -1,37 +0,0 @@ -# encoding: utf-8 - -#-- -# Copyright 2013-2016 DataStax, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -#++ - -module Cassandra - module Protocol - module Timestamp - # A generator is used to create client-timestamps (in the form of long integers) to send with C* requests when - # the `:client_timestamps` cluster option is set to true. - # - # @abstract A timestamp generator given to {Cassandra.cluster} doesn't need to be - # a subclass of this class, but needs to implement the same methods. This - # class exists only for documentation purposes. - class Generator - # @!method next - # - # Create a new timestamp, as a 64-bit integer. Calls must return monotonically increasing values. - # - # @return [Integer] an integer representing a timestamp. - end - end - end -end diff --git a/lib/cassandra/protocol/timestamp/jruby_time_based_generator.rb b/lib/cassandra/protocol/timestamp/jruby_time_based_generator.rb deleted file mode 100644 index c3d397b9d..000000000 --- a/lib/cassandra/protocol/timestamp/jruby_time_based_generator.rb +++ /dev/null @@ -1,59 +0,0 @@ -# encoding: utf-8 - -#-- -# Copyright 2013-2016 DataStax, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -#++ - -module Cassandra - module Protocol - module Timestamp - # In JRuby, {::Time} has millisecond precision. We require client timestamps to have microsecond precision to - # minimize clashes in C*. This generator keeps track of the last generated timestamp, and if the current-time - # is within the same millisecond as the last, it fills the microsecond portion of the new timestamp with the - # value of an incrementing counter. - # - # For example, if the generator triggers twice at time 12345678000 (microsecond granularity, but ms precisions - # as shown by 0's for the three least-significant digits), it'll return 12345678000 and 12345678001. - class JRubyTimeBasedGenerator - include MonitorMixin - - # @private - def initialize - mon_initialize - @last = 0 - end - - # Create a new timestamp, as a 64-bit integer. - # - # @return [Integer] an integer representing a timestamp in microseconds. - def next - now = ::Time.now - now_millis = now.tv_sec * 1000 + now.tv_usec / 1000 - synchronize do - millis = @last / 1000 - counter = @last % 1000 - if millis >= now_millis - counter += 1 - else - millis = now_millis - counter = 0 - end - @last = millis * 1000 + counter - end - end - end - end - end -end diff --git a/lib/cassandra/timestamp_generator.rb b/lib/cassandra/timestamp_generator.rb new file mode 100644 index 000000000..1c5e93a56 --- /dev/null +++ b/lib/cassandra/timestamp_generator.rb @@ -0,0 +1,35 @@ +# encoding: utf-8 + +#-- +# Copyright 2013-2016 DataStax, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + +module Cassandra + # A generator is used to create client-timestamps (in the form of long integers) to send with C* requests when + # the `:client_timestamps` cluster option is set to true. + # + # @abstract A timestamp generator given to {Cassandra.cluster} doesn't need to include this module, but needs to + # implement the same methods. This module exists only for documentation purposes. + module TimestampGenerator + # @!method next + # + # Create a new timestamp, as a 64-bit integer. Calls must return monotonically increasing values. + # + # @return [Integer] an integer representing a timestamp in microseconds. + end +end + +require 'cassandra/timestamp_generator/ticking_on_duplicate_generator' +require 'cassandra/timestamp_generator/simple' diff --git a/lib/cassandra/protocol/timestamp/time_based_generator.rb b/lib/cassandra/timestamp_generator/simple.rb similarity index 94% rename from lib/cassandra/protocol/timestamp/time_based_generator.rb rename to lib/cassandra/timestamp_generator/simple.rb index ff6431f31..b6ef9576e 100644 --- a/lib/cassandra/protocol/timestamp/time_based_generator.rb +++ b/lib/cassandra/timestamp_generator/simple.rb @@ -17,12 +17,13 @@ #++ module Cassandra - module Protocol - module Timestamp + module TimestampGenerator # Generate long integer timestamps from current time. This implementation relies on the {::Time} class to return # microsecond precision time. # @note It is not appropriate for use with JRuby because its {::Time#now} returns millisecond precision time. - class TimeBasedGenerator + class Simple + include TimestampGenerator + # Create a new timestamp, as a 64-bit integer. This is just a wrapper around Time::now. # # @return [Integer] an integer representing a timestamp in microseconds. @@ -34,5 +35,4 @@ def next end end end - end end diff --git a/lib/cassandra/timestamp_generator/ticking_on_duplicate_generator.rb b/lib/cassandra/timestamp_generator/ticking_on_duplicate_generator.rb new file mode 100644 index 000000000..576f78020 --- /dev/null +++ b/lib/cassandra/timestamp_generator/ticking_on_duplicate_generator.rb @@ -0,0 +1,58 @@ +# encoding: utf-8 + +#-- +# Copyright 2013-2016 DataStax, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + +module Cassandra + module TimestampGenerator + # In JRuby, {::Time} has millisecond precision. We require client timestamps to have microsecond precision to + # minimize clashes in C*. This generator keeps track of the last generated timestamp, and if the current-time + # is within the same millisecond as the last, it fills the microsecond portion of the new timestamp with the + # value of an incrementing counter. + # + # For example, if the generator triggers twice at time 12345678000 (microsecond granularity, but ms precisions + # as shown by 0's for the three least-significant digits), it'll return 12345678000 and 12345678001. + class TickingOnDuplicate + include MonitorMixin + include TimestampGenerator + + # @private + def initialize + mon_initialize + @last = 0 + end + + # Create a new timestamp, as a 64-bit integer. + # + # @return [Integer] an integer representing a timestamp in microseconds. + def next + now = ::Time.now + now_millis = now.tv_sec * 1000 + now.tv_usec / 1000 + synchronize do + millis = @last / 1000 + counter = @last % 1000 + if millis >= now_millis + counter += 1 + else + millis = now_millis + counter = 0 + end + @last = millis * 1000 + counter + end + end + end + end +end From 458d06cd458fd87c45f558370188c38b821a46f1 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Tue, 3 May 2016 13:10:56 -0700 Subject: [PATCH 057/196] RUBY-214 - JRuby timestamps have millisecond precision and can cause client timestamp collisions. * Various moving/renaming of classes to clarify their use. --- lib/cassandra/timestamp_generator.rb | 8 +++++--- ..._on_duplicate_generator.rb => ticking_on_duplicate.rb} | 0 2 files changed, 5 insertions(+), 3 deletions(-) rename lib/cassandra/timestamp_generator/{ticking_on_duplicate_generator.rb => ticking_on_duplicate.rb} (100%) diff --git a/lib/cassandra/timestamp_generator.rb b/lib/cassandra/timestamp_generator.rb index 1c5e93a56..53b3a930f 100644 --- a/lib/cassandra/timestamp_generator.rb +++ b/lib/cassandra/timestamp_generator.rb @@ -23,13 +23,15 @@ module Cassandra # @abstract A timestamp generator given to {Cassandra.cluster} doesn't need to include this module, but needs to # implement the same methods. This module exists only for documentation purposes. module TimestampGenerator - # @!method next - # # Create a new timestamp, as a 64-bit integer. Calls must return monotonically increasing values. # # @return [Integer] an integer representing a timestamp in microseconds. + # @raise [NotImplementedError] if a class including this module does not define this method. + def next + raise NotImplementedError, "#{self.class} class must implement the 'next' method" + end end end -require 'cassandra/timestamp_generator/ticking_on_duplicate_generator' +require 'cassandra/timestamp_generator/ticking_on_duplicate' require 'cassandra/timestamp_generator/simple' diff --git a/lib/cassandra/timestamp_generator/ticking_on_duplicate_generator.rb b/lib/cassandra/timestamp_generator/ticking_on_duplicate.rb similarity index 100% rename from lib/cassandra/timestamp_generator/ticking_on_duplicate_generator.rb rename to lib/cassandra/timestamp_generator/ticking_on_duplicate.rb From ab0686011ba4fb269aff674fefebb97978e42f75 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Tue, 3 May 2016 15:18:12 -0700 Subject: [PATCH 058/196] RUBY-214 - JRuby timestamps have millisecond precision and can cause client timestamp collisions. * Consolidate timestamp_generator option into client_timestamps option. --- CHANGELOG.md | 2 +- integration/session_test.rb | 2 +- lib/cassandra.rb | 47 +++++++++++++++++---------- lib/cassandra/cluster.rb | 7 ++-- lib/cassandra/cluster/client.rb | 10 +++--- lib/cassandra/cluster/options.rb | 8 +++-- lib/cassandra/driver.rb | 10 +----- spec/cassandra/cluster/client_spec.rb | 2 +- spec/cassandra/cluster_spec.rb | 2 +- spec/cassandra_spec.rb | 18 +++++----- spec/regressions/RUBY-189_spec.rb | 2 +- 11 files changed, 56 insertions(+), 54 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c62e7962..bb51fbb0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # master Features: -* Add timestamp_generator cluster configuration option to allow user to specify his own generator for client timestamps. +* Expand :client_timestamps cluster configuration option to allow user to specify his own generator for client timestamps. Bug Fixes: * [RUBY-214](https://datastax-oss.atlassian.net/browse/RUBY-214) Client timestamps in JRuby are not fine-grained enough, causing timestamp collisions and lost rows in C*. diff --git a/integration/session_test.rb b/integration/session_test.rb index 895dc04a5..db2aef588 100644 --- a/integration/session_test.rb +++ b/integration/session_test.rb @@ -580,7 +580,7 @@ def test_client_side_timestamps_with_past_timestamp setup_schema begin - cluster = Cassandra.cluster(client_timestamps: true) + cluster = Cassandra.cluster(client_timestamps: :simple) session = cluster.connect("simplex") # Insert in the present diff --git a/lib/cassandra.rb b/lib/cassandra.rb index ed2c29a4f..47473167f 100644 --- a/lib/cassandra.rb +++ b/lib/cassandra.rb @@ -91,7 +91,6 @@ module Cassandra :ssl, :synchronize_schema, :timeout, - :timestamp_generator, :trace, :username ].freeze @@ -191,16 +190,14 @@ module Cassandra # nodes. By default, this is auto-negotiated to the lowest common protocol version # that all nodes in `:hosts` speak. # - # @option options [Boolean] :client_timestamps (false) whether the driver - # should send timestamps for each executed statement. Enabling this setting - # helps mitigate Cassandra cluster clock skew because the timestamp of - # the client machine will be used. This does not help mitigate application - # cluster clock skew. - # - # @option options [Cassandra::TimestampGenerator] :timestamp_generator the timestamp generator to use for - # client timestamps (if :client_timestamps is true). Defaults to - # {Cassandra::TimestampGenerator::Simple} for all Ruby flavors - # except JRuby. On JRuby, it defaults to {Cassandra::TimestampGenerator::TickingOnDuplicate}. + # @option options [Boolean, Cassandra::TimestampGenerator] :client_timestamps (false) whether the driver + # should send timestamps for each executed statement and possibly which timestamp generator to use. Enabling this + # setting helps mitigate Cassandra cluster clock skew because the timestamp of the client machine will be used. + # This does not help mitigate application cluster clock skew. Also accepts an initialized + # {Cassandra::TimestampGenerator}, `:simple` (indicating an instance of {Cassandra::TimestampGenerator::Simple}), + # or `:monotonic` (indicating an instance of {Cassandra::TimestampGenerator::TickingOnDuplicate}). If set to true, + # it defaults to {Cassandra::TimestampGenerator::Simple} for all Ruby flavors except JRuby. On JRuby, it defaults to + # {Cassandra::TimestampGenerator::TickingOnDuplicate}. # # @option options [Boolean] :synchronize_schema (true) whether the driver # should automatically keep schema metadata synchronized. When enabled, the @@ -688,14 +685,28 @@ def self.validate_and_massage_options(options) end options[:synchronize_schema] = !!options[:synchronize_schema] if options.key?(:synchronize_schema) - options[:client_timestamps] = !!options[:client_timestamps] if options.key?(:client_timestamps) - - if options.key?(:timestamp_generator) - timestamp_generator = options[:timestamp_generator] - Util.assert_responds_to(:next, timestamp_generator) do - ":timestamp_generator #{timestamp_generator.inspect} must respond to " \ - ":next, but doesn't" + if options.key?(:client_timestamps) + options[:client_timestamps] = case options[:client_timestamps] + when true + if RUBY_ENGINE == 'jruby' + Cassandra::TimestampGenerator::TickingOnDuplicate.new + else + Cassandra::TimestampGenerator::Simple.new + end + when :simple + Cassandra::TimestampGenerator::Simple.new + when :monotonic + Cassandra::TimestampGenerator::TickingOnDuplicate.new + else + options[:client_timestamps] + end + + if options[:client_timestamps] + Util.assert_responds_to(:next, options[:client_timestamps]) do + ":client_timestamps #{options[:client_timestamps].inspect} must be a boolean, :simple, :monotonic, or " \ + "an object that responds to :next" + end end end diff --git a/lib/cassandra/cluster.rb b/lib/cassandra/cluster.rb index f99d7bd02..612815642 100644 --- a/lib/cassandra/cluster.rb +++ b/lib/cassandra/cluster.rb @@ -41,8 +41,7 @@ def initialize(logger, retry_policy, address_resolution_policy, connector, - futures_factory, - timestamp_generator) + futures_factory) @logger = logger @io_reactor = io_reactor @executor = executor @@ -58,7 +57,6 @@ def initialize(logger, @address_resolver = address_resolution_policy @connector = connector @futures = futures_factory - @timestamp_generator = timestamp_generator @control_connection.on_close do |_cause| begin @@ -206,8 +204,7 @@ def connect_async(keyspace = nil) @retry_policy, @address_resolver, @connection_options, - @futures, - @timestamp_generator) + @futures) session = Session.new(client, @execution_options, @futures) promise = @futures.promise diff --git a/lib/cassandra/cluster/client.rb b/lib/cassandra/cluster/client.rb index 4321746ff..9dd1aad6a 100644 --- a/lib/cassandra/cluster/client.rb +++ b/lib/cassandra/cluster/client.rb @@ -34,8 +34,7 @@ def initialize(logger, retry_policy, address_resolution_policy, connection_options, - futures_factory, - timestamp_generator) + futures_factory) @logger = logger @registry = cluster_registry @schema = cluster_schema @@ -53,7 +52,6 @@ def initialize(logger, @pending_connections = ::Hash.new @keyspace = nil @state = :idle - @timestamp_generator = timestamp_generator mon_initialize end @@ -233,7 +231,7 @@ def query(statement, options) timestamp = nil if @connection_options.client_timestamps? && @connection_options.protocol_version > 2 - timestamp = @timestamp_generator.next + timestamp = @connection_options.client_timestamps.next end payload = nil payload = options.payload if @connection_options.protocol_version >= 4 @@ -291,7 +289,7 @@ def execute(statement, options) timestamp = nil if @connection_options.client_timestamps? && @connection_options.protocol_version > 2 - timestamp = @timestamp_generator.next + timestamp = @connection_options.client_timestamps.next end payload = nil payload = options.payload if @connection_options.protocol_version >= 4 @@ -329,7 +327,7 @@ def batch(statement, options) timestamp = nil if @connection_options.client_timestamps? && @connection_options.protocol_version > 2 - timestamp = @timestamp_generator.next + timestamp = @connection_options.client_timestamps.next end payload = nil payload = options.payload if @connection_options.protocol_version >= 4 diff --git a/lib/cassandra/cluster/options.rb b/lib/cassandra/cluster/options.rb index 739fd62d3..49b488d29 100644 --- a/lib/cassandra/cluster/options.rb +++ b/lib/cassandra/cluster/options.rb @@ -22,10 +22,10 @@ class Cluster class Options extend AttrBoolean - attr_reader :auth_provider, :compressor, :connect_timeout, :credentials, + attr_reader :auth_provider, :client_timestamps, :compressor, :connect_timeout, :credentials, :heartbeat_interval, :idle_timeout, :port, :schema_refresh_delay, :schema_refresh_timeout, :ssl - attr_boolean :protocol_negotiable, :synchronize_schema, :client_timestamps, :nodelay + attr_boolean :protocol_negotiable, :synchronize_schema, :nodelay attr_accessor :protocol_version @@ -76,6 +76,10 @@ def initialize(logger, @protocol_version ||= 4 end + def client_timestamps? + !@client_timestamps.nil? && @client_timestamps + end + def compression @compressor && @compressor.algorithm end diff --git a/lib/cassandra/driver.rb b/lib/cassandra/driver.rb index ef4ba387a..e57e4f2c2 100644 --- a/lib/cassandra/driver.rb +++ b/lib/cassandra/driver.rb @@ -107,8 +107,7 @@ def self.let(name, &block) retry_policy, address_resolution_policy, connector, - futures_factory, - timestamp_generator) + futures_factory) end let(:execution_options) do @@ -174,13 +173,6 @@ def self.let(name, &block) let(:shuffle_replicas) { true } let(:client_timestamps) { false } let(:nodelay) { true } - let(:timestamp_generator) do - if RUBY_ENGINE == 'jruby' - Cassandra::TimestampGenerator::TickingOnDuplicate.new - else - Cassandra::TimestampGenerator::Simple.new - end - end let(:connections_per_local_node) { nil } let(:connections_per_remote_node) { nil } let(:requests_per_connection) { nil } diff --git a/spec/cassandra/cluster/client_spec.rb b/spec/cassandra/cluster/client_spec.rb index 133b438c9..1823cd03f 100644 --- a/spec/cassandra/cluster/client_spec.rb +++ b/spec/cassandra/cluster/client_spec.rb @@ -40,7 +40,7 @@ class Cluster } } let(:driver) { Driver.new(driver_settings) } - let(:client) { Client.new(driver.logger, driver.cluster_registry, driver.cluster_schema, driver.io_reactor, driver.connector, driver.load_balancing_policy, driver.reconnection_policy, driver.retry_policy, driver.address_resolution_policy, driver.connection_options, driver.futures_factory, driver.timestamp_generator) } + let(:client) { Client.new(driver.logger, driver.cluster_registry, driver.cluster_schema, driver.io_reactor, driver.connector, driver.load_balancing_policy, driver.reconnection_policy, driver.retry_policy, driver.address_resolution_policy, driver.connection_options, driver.futures_factory) } before do io_reactor.connection_options = driver.connection_options diff --git a/spec/cassandra/cluster_spec.rb b/spec/cassandra/cluster_spec.rb index 9d63920fc..e451e985e 100644 --- a/spec/cassandra/cluster_spec.rb +++ b/spec/cassandra/cluster_spec.rb @@ -33,7 +33,7 @@ module Cassandra }) } - let(:cluster) { Cluster.new(driver.logger, io_reactor, executor, control_connection, cluster_registry, driver.cluster_schema, driver.cluster_metadata, driver.execution_options, driver.connection_options, load_balancing_policy, driver.reconnection_policy, driver.retry_policy, driver.address_resolution_policy, driver.connector, driver.futures_factory, driver.timestamp_generator) } + let(:cluster) { Cluster.new(driver.logger, io_reactor, executor, control_connection, cluster_registry, driver.cluster_schema, driver.cluster_metadata, driver.execution_options, driver.connection_options, load_balancing_policy, driver.reconnection_policy, driver.retry_policy, driver.address_resolution_policy, driver.connector, driver.futures_factory) } before do io_reactor.connection_options = driver.connection_options diff --git a/spec/cassandra_spec.rb b/spec/cassandra_spec.rb index 781096b6f..f6d259637 100644 --- a/spec/cassandra_spec.rb +++ b/spec/cassandra_spec.rb @@ -373,20 +373,20 @@ def resolve expect(C.validate(synchronize_schema: 0)).to eq({synchronize_schema: true}) end - it 'should massage :client_timestamps to a boolean' do - expect(C.validate(client_timestamps: nil)).to eq({client_timestamps: false}) - expect(C.validate(client_timestamps: 1)).to eq({client_timestamps: true}) - expect(C.validate(client_timestamps: 0)).to eq({client_timestamps: true}) - end + it 'should massage :client_timestamps to a generator class or nil' do + expect(C.validate(client_timestamps: nil)).to eq({client_timestamps: nil}) + + expected_class = RUBY_ENGINE == 'jruby' ? + Cassandra::TimestampGenerator::TickingOnDuplicate : + Cassandra::TimestampGenerator::Simple + expect(C.validate(client_timestamps: true)[:client_timestamps]).to be_instance_of(expected_class) + expect { C.validate(client_timestamps: Object.new) }.to raise_error(ArgumentError) - it 'should validate :timestamp_generator' do valid_generator = Object.new def valid_generator.next 42 end - expect { C.validate(timestamp_generator: Object.new) }.to raise_error(ArgumentError) - expect(C.validate(timestamp_generator: valid_generator)). - to eq({ timestamp_generator: valid_generator }) + expect(C.validate(client_timestamps: valid_generator)).to eq({ client_timestamps: valid_generator }) end it 'should validate :connections_per_local_node' do diff --git a/spec/regressions/RUBY-189_spec.rb b/spec/regressions/RUBY-189_spec.rb index 4915caf7e..420a688d5 100644 --- a/spec/regressions/RUBY-189_spec.rb +++ b/spec/regressions/RUBY-189_spec.rb @@ -43,7 +43,7 @@ class Cluster let(:client) { Client.new(driver.logger, driver.cluster_registry, driver.cluster_schema, driver.io_reactor, driver.connector, driver.load_balancing_policy, driver.reconnection_policy, driver.retry_policy, - driver.address_resolution_policy, driver.connection_options, driver.futures_factory, driver.timestamp_generator) + driver.address_resolution_policy, driver.connection_options, driver.futures_factory) } let(:promise) { double('promise') } From 0e8ae0311e2cf773104be5d1d709d14563e2be73 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Tue, 3 May 2016 17:28:15 -0700 Subject: [PATCH 059/196] RUBY-214 - JRuby timestamps have millisecond precision and can cause client timestamp collisions. * Map client_timestamps to timestamp_generator entry in options hash. --- lib/cassandra.rb | 10 +++++++--- lib/cassandra/cluster.rb | 7 +++++-- lib/cassandra/cluster/client.rb | 11 +++++++---- lib/cassandra/cluster/options.rb | 8 ++------ lib/cassandra/driver.rb | 7 ++++--- spec/cassandra/cluster/client_spec.rb | 2 +- spec/cassandra/cluster_spec.rb | 2 +- spec/cassandra_spec.rb | 10 ++++++---- spec/regressions/RUBY-189_spec.rb | 2 +- 9 files changed, 34 insertions(+), 25 deletions(-) diff --git a/lib/cassandra.rb b/lib/cassandra.rb index 47473167f..902115a22 100644 --- a/lib/cassandra.rb +++ b/lib/cassandra.rb @@ -687,27 +687,31 @@ def self.validate_and_massage_options(options) options[:synchronize_schema] = !!options[:synchronize_schema] if options.key?(:synchronize_schema) if options.key?(:client_timestamps) - options[:client_timestamps] = case options[:client_timestamps] + options[:timestamp_generator] = case options[:client_timestamps] when true if RUBY_ENGINE == 'jruby' Cassandra::TimestampGenerator::TickingOnDuplicate.new else Cassandra::TimestampGenerator::Simple.new end + when false + nil when :simple Cassandra::TimestampGenerator::Simple.new when :monotonic Cassandra::TimestampGenerator::TickingOnDuplicate.new else + # The value must be a generator instance. options[:client_timestamps] end - if options[:client_timestamps] - Util.assert_responds_to(:next, options[:client_timestamps]) do + if options[:timestamp_generator] + Util.assert_responds_to(:next, options[:timestamp_generator]) do ":client_timestamps #{options[:client_timestamps].inspect} must be a boolean, :simple, :monotonic, or " \ "an object that responds to :next" end end + options.delete(:client_timestamps) end if options.key?(:connections_per_local_node) diff --git a/lib/cassandra/cluster.rb b/lib/cassandra/cluster.rb index 612815642..f99d7bd02 100644 --- a/lib/cassandra/cluster.rb +++ b/lib/cassandra/cluster.rb @@ -41,7 +41,8 @@ def initialize(logger, retry_policy, address_resolution_policy, connector, - futures_factory) + futures_factory, + timestamp_generator) @logger = logger @io_reactor = io_reactor @executor = executor @@ -57,6 +58,7 @@ def initialize(logger, @address_resolver = address_resolution_policy @connector = connector @futures = futures_factory + @timestamp_generator = timestamp_generator @control_connection.on_close do |_cause| begin @@ -204,7 +206,8 @@ def connect_async(keyspace = nil) @retry_policy, @address_resolver, @connection_options, - @futures) + @futures, + @timestamp_generator) session = Session.new(client, @execution_options, @futures) promise = @futures.promise diff --git a/lib/cassandra/cluster/client.rb b/lib/cassandra/cluster/client.rb index 9dd1aad6a..f5faaefdb 100644 --- a/lib/cassandra/cluster/client.rb +++ b/lib/cassandra/cluster/client.rb @@ -34,7 +34,8 @@ def initialize(logger, retry_policy, address_resolution_policy, connection_options, - futures_factory) + futures_factory, + timestamp_generator) @logger = logger @registry = cluster_registry @schema = cluster_schema @@ -52,6 +53,7 @@ def initialize(logger, @pending_connections = ::Hash.new @keyspace = nil @state = :idle + @timestamp_generator = timestamp_generator mon_initialize end @@ -231,7 +233,7 @@ def query(statement, options) timestamp = nil if @connection_options.client_timestamps? && @connection_options.protocol_version > 2 - timestamp = @connection_options.client_timestamps.next + timestamp = @timestamp_generator.next end payload = nil payload = options.payload if @connection_options.protocol_version >= 4 @@ -289,7 +291,8 @@ def execute(statement, options) timestamp = nil if @connection_options.client_timestamps? && @connection_options.protocol_version > 2 - timestamp = @connection_options.client_timestamps.next + timestamp = @timestamp_generator.next + Thread.current[:timestamps] << timestamp end payload = nil payload = options.payload if @connection_options.protocol_version >= 4 @@ -327,7 +330,7 @@ def batch(statement, options) timestamp = nil if @connection_options.client_timestamps? && @connection_options.protocol_version > 2 - timestamp = @connection_options.client_timestamps.next + timestamp = @timestamp_generator.next end payload = nil payload = options.payload if @connection_options.protocol_version >= 4 diff --git a/lib/cassandra/cluster/options.rb b/lib/cassandra/cluster/options.rb index 49b488d29..739fd62d3 100644 --- a/lib/cassandra/cluster/options.rb +++ b/lib/cassandra/cluster/options.rb @@ -22,10 +22,10 @@ class Cluster class Options extend AttrBoolean - attr_reader :auth_provider, :client_timestamps, :compressor, :connect_timeout, :credentials, + attr_reader :auth_provider, :compressor, :connect_timeout, :credentials, :heartbeat_interval, :idle_timeout, :port, :schema_refresh_delay, :schema_refresh_timeout, :ssl - attr_boolean :protocol_negotiable, :synchronize_schema, :nodelay + attr_boolean :protocol_negotiable, :synchronize_schema, :client_timestamps, :nodelay attr_accessor :protocol_version @@ -76,10 +76,6 @@ def initialize(logger, @protocol_version ||= 4 end - def client_timestamps? - !@client_timestamps.nil? && @client_timestamps - end - def compression @compressor && @compressor.algorithm end diff --git a/lib/cassandra/driver.rb b/lib/cassandra/driver.rb index e57e4f2c2..129345726 100644 --- a/lib/cassandra/driver.rb +++ b/lib/cassandra/driver.rb @@ -107,7 +107,8 @@ def self.let(name, &block) retry_policy, address_resolution_policy, connector, - futures_factory) + futures_factory, + timestamp_generator) end let(:execution_options) do @@ -135,7 +136,7 @@ def self.let(name, &block) synchronize_schema, schema_refresh_delay, schema_refresh_timeout, - client_timestamps, + !timestamp_generator.nil?, nodelay, requests_per_connection ) @@ -171,8 +172,8 @@ def self.let(name, &block) let(:schema_refresh_timeout) { 10 } let(:thread_pool_size) { 4 } let(:shuffle_replicas) { true } - let(:client_timestamps) { false } let(:nodelay) { true } + let(:timestamp_generator) { nil } let(:connections_per_local_node) { nil } let(:connections_per_remote_node) { nil } let(:requests_per_connection) { nil } diff --git a/spec/cassandra/cluster/client_spec.rb b/spec/cassandra/cluster/client_spec.rb index 1823cd03f..133b438c9 100644 --- a/spec/cassandra/cluster/client_spec.rb +++ b/spec/cassandra/cluster/client_spec.rb @@ -40,7 +40,7 @@ class Cluster } } let(:driver) { Driver.new(driver_settings) } - let(:client) { Client.new(driver.logger, driver.cluster_registry, driver.cluster_schema, driver.io_reactor, driver.connector, driver.load_balancing_policy, driver.reconnection_policy, driver.retry_policy, driver.address_resolution_policy, driver.connection_options, driver.futures_factory) } + let(:client) { Client.new(driver.logger, driver.cluster_registry, driver.cluster_schema, driver.io_reactor, driver.connector, driver.load_balancing_policy, driver.reconnection_policy, driver.retry_policy, driver.address_resolution_policy, driver.connection_options, driver.futures_factory, driver.timestamp_generator) } before do io_reactor.connection_options = driver.connection_options diff --git a/spec/cassandra/cluster_spec.rb b/spec/cassandra/cluster_spec.rb index e451e985e..9d63920fc 100644 --- a/spec/cassandra/cluster_spec.rb +++ b/spec/cassandra/cluster_spec.rb @@ -33,7 +33,7 @@ module Cassandra }) } - let(:cluster) { Cluster.new(driver.logger, io_reactor, executor, control_connection, cluster_registry, driver.cluster_schema, driver.cluster_metadata, driver.execution_options, driver.connection_options, load_balancing_policy, driver.reconnection_policy, driver.retry_policy, driver.address_resolution_policy, driver.connector, driver.futures_factory) } + let(:cluster) { Cluster.new(driver.logger, io_reactor, executor, control_connection, cluster_registry, driver.cluster_schema, driver.cluster_metadata, driver.execution_options, driver.connection_options, load_balancing_policy, driver.reconnection_policy, driver.retry_policy, driver.address_resolution_policy, driver.connector, driver.futures_factory, driver.timestamp_generator) } before do io_reactor.connection_options = driver.connection_options diff --git a/spec/cassandra_spec.rb b/spec/cassandra_spec.rb index f6d259637..c137f6ab5 100644 --- a/spec/cassandra_spec.rb +++ b/spec/cassandra_spec.rb @@ -373,20 +373,22 @@ def resolve expect(C.validate(synchronize_schema: 0)).to eq({synchronize_schema: true}) end - it 'should massage :client_timestamps to a generator class or nil' do - expect(C.validate(client_timestamps: nil)).to eq({client_timestamps: nil}) + it 'should map :client_timestamps to a generator class or nil' do + expect(C.validate(client_timestamps: nil)).to eq({timestamp_generator: nil}) + expect(C.validate(client_timestamps: false)).to eq({timestamp_generator: nil}) + expect(C.validate({})).to eq({}) expected_class = RUBY_ENGINE == 'jruby' ? Cassandra::TimestampGenerator::TickingOnDuplicate : Cassandra::TimestampGenerator::Simple - expect(C.validate(client_timestamps: true)[:client_timestamps]).to be_instance_of(expected_class) + expect(C.validate(client_timestamps: true)[:timestamp_generator]).to be_instance_of(expected_class) expect { C.validate(client_timestamps: Object.new) }.to raise_error(ArgumentError) valid_generator = Object.new def valid_generator.next 42 end - expect(C.validate(client_timestamps: valid_generator)).to eq({ client_timestamps: valid_generator }) + expect(C.validate(client_timestamps: valid_generator)).to eq({ timestamp_generator: valid_generator }) end it 'should validate :connections_per_local_node' do diff --git a/spec/regressions/RUBY-189_spec.rb b/spec/regressions/RUBY-189_spec.rb index 420a688d5..4915caf7e 100644 --- a/spec/regressions/RUBY-189_spec.rb +++ b/spec/regressions/RUBY-189_spec.rb @@ -43,7 +43,7 @@ class Cluster let(:client) { Client.new(driver.logger, driver.cluster_registry, driver.cluster_schema, driver.io_reactor, driver.connector, driver.load_balancing_policy, driver.reconnection_policy, driver.retry_policy, - driver.address_resolution_policy, driver.connection_options, driver.futures_factory) + driver.address_resolution_policy, driver.connection_options, driver.futures_factory, driver.timestamp_generator) } let(:promise) { double('promise') } From 33dca873779b337d0bf48a84994ef610d95506e1 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Tue, 3 May 2016 17:28:58 -0700 Subject: [PATCH 060/196] RUBY-214 - JRuby timestamps have millisecond precision and can cause client timestamp collisions. * Remove accidentally remaining debug statement --- lib/cassandra/cluster/client.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/cassandra/cluster/client.rb b/lib/cassandra/cluster/client.rb index f5faaefdb..4321746ff 100644 --- a/lib/cassandra/cluster/client.rb +++ b/lib/cassandra/cluster/client.rb @@ -292,7 +292,6 @@ def execute(statement, options) if @connection_options.client_timestamps? && @connection_options.protocol_version > 2 timestamp = @timestamp_generator.next - Thread.current[:timestamps] << timestamp end payload = nil payload = options.payload if @connection_options.protocol_version >= 4 From f141b376566bdf51f1c901cc60ece5f052fadae7 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Tue, 3 May 2016 18:50:29 -0700 Subject: [PATCH 061/196] RUBY-214 - JRuby timestamps have millisecond precision and can cause client timestamp collisions. * internal refinements --- lib/cassandra.rb | 43 +++++++++++---------- lib/cassandra/cluster/client.rb | 18 ++------- lib/cassandra/cluster/options.rb | 4 +- lib/cassandra/driver.rb | 1 - lib/cassandra/timestamp_generator/simple.rb | 30 +++++++------- spec/cassandra/cluster/options_spec.rb | 2 +- 6 files changed, 42 insertions(+), 56 deletions(-) diff --git a/lib/cassandra.rb b/lib/cassandra.rb index 902115a22..e5db8591c 100644 --- a/lib/cassandra.rb +++ b/lib/cassandra.rb @@ -687,31 +687,32 @@ def self.validate_and_massage_options(options) options[:synchronize_schema] = !!options[:synchronize_schema] if options.key?(:synchronize_schema) if options.key?(:client_timestamps) - options[:timestamp_generator] = case options[:client_timestamps] - when true - if RUBY_ENGINE == 'jruby' - Cassandra::TimestampGenerator::TickingOnDuplicate.new - else - Cassandra::TimestampGenerator::Simple.new - end - when false - nil - when :simple - Cassandra::TimestampGenerator::Simple.new - when :monotonic - Cassandra::TimestampGenerator::TickingOnDuplicate.new - else - # The value must be a generator instance. - options[:client_timestamps] - end - - if options[:timestamp_generator] - Util.assert_responds_to(:next, options[:timestamp_generator]) do + timestamp_generator = case options[:client_timestamps] + when true + if RUBY_ENGINE == 'jruby' + Cassandra::TimestampGenerator::TickingOnDuplicate.new + else + Cassandra::TimestampGenerator::Simple.new + end + when false + nil + when :simple + Cassandra::TimestampGenerator::Simple.new + when :monotonic + Cassandra::TimestampGenerator::TickingOnDuplicate.new + else + # The value must be a generator instance. + options[:client_timestamps] + end + + if timestamp_generator + Util.assert_responds_to(:next, timestamp_generator) do ":client_timestamps #{options[:client_timestamps].inspect} must be a boolean, :simple, :monotonic, or " \ - "an object that responds to :next" + 'an object that responds to :next' end end options.delete(:client_timestamps) + options[:timestamp_generator] = timestamp_generator end if options.key?(:connections_per_local_node) diff --git a/lib/cassandra/cluster/client.rb b/lib/cassandra/cluster/client.rb index 4321746ff..95be7c3bb 100644 --- a/lib/cassandra/cluster/client.rb +++ b/lib/cassandra/cluster/client.rb @@ -230,11 +230,7 @@ def query(statement, options) 'Apache Cassandra')) end - timestamp = nil - if @connection_options.client_timestamps? && - @connection_options.protocol_version > 2 - timestamp = @timestamp_generator.next - end + timestamp = @timestamp_generator.next if @timestamp_generator && @connection_options.protocol_version > 2 payload = nil payload = options.payload if @connection_options.protocol_version >= 4 request = Protocol::QueryRequest.new(statement.cql, @@ -288,11 +284,7 @@ def prepare(cql, options) end def execute(statement, options) - timestamp = nil - if @connection_options.client_timestamps? && - @connection_options.protocol_version > 2 - timestamp = @timestamp_generator.next - end + timestamp = @timestamp_generator.next if @timestamp_generator && @connection_options.protocol_version > 2 payload = nil payload = options.payload if @connection_options.protocol_version >= 4 timeout = options.timeout @@ -326,11 +318,7 @@ def batch(statement, options) 'Apache Cassandra')) end - timestamp = nil - if @connection_options.client_timestamps? && - @connection_options.protocol_version > 2 - timestamp = @timestamp_generator.next - end + timestamp = @timestamp_generator.next if @timestamp_generator && @connection_options.protocol_version > 2 payload = nil payload = options.payload if @connection_options.protocol_version >= 4 timeout = options.timeout diff --git a/lib/cassandra/cluster/options.rb b/lib/cassandra/cluster/options.rb index 739fd62d3..78cfb544f 100644 --- a/lib/cassandra/cluster/options.rb +++ b/lib/cassandra/cluster/options.rb @@ -25,7 +25,7 @@ class Options attr_reader :auth_provider, :compressor, :connect_timeout, :credentials, :heartbeat_interval, :idle_timeout, :port, :schema_refresh_delay, :schema_refresh_timeout, :ssl - attr_boolean :protocol_negotiable, :synchronize_schema, :client_timestamps, :nodelay + attr_boolean :protocol_negotiable, :synchronize_schema, :nodelay attr_accessor :protocol_version @@ -44,7 +44,6 @@ def initialize(logger, synchronize_schema, schema_refresh_delay, schema_refresh_timeout, - client_timestamps, nodelay, requests_per_connection) @logger = logger @@ -60,7 +59,6 @@ def initialize(logger, @synchronize_schema = synchronize_schema @schema_refresh_delay = schema_refresh_delay @schema_refresh_timeout = schema_refresh_timeout - @client_timestamps = client_timestamps @nodelay = nodelay @connections_per_local_node = connections_per_local_node diff --git a/lib/cassandra/driver.rb b/lib/cassandra/driver.rb index 129345726..b7c0e741e 100644 --- a/lib/cassandra/driver.rb +++ b/lib/cassandra/driver.rb @@ -136,7 +136,6 @@ def self.let(name, &block) synchronize_schema, schema_refresh_delay, schema_refresh_timeout, - !timestamp_generator.nil?, nodelay, requests_per_connection ) diff --git a/lib/cassandra/timestamp_generator/simple.rb b/lib/cassandra/timestamp_generator/simple.rb index b6ef9576e..8838e6803 100644 --- a/lib/cassandra/timestamp_generator/simple.rb +++ b/lib/cassandra/timestamp_generator/simple.rb @@ -17,22 +17,22 @@ #++ module Cassandra - module TimestampGenerator - # Generate long integer timestamps from current time. This implementation relies on the {::Time} class to return - # microsecond precision time. - # @note It is not appropriate for use with JRuby because its {::Time#now} returns millisecond precision time. - class Simple - include TimestampGenerator + module TimestampGenerator + # Generate long integer timestamps from current time. This implementation relies on the {::Time} class to return + # microsecond precision time. + # @note It is not appropriate for use with JRuby because its {::Time#now} returns millisecond precision time. + class Simple + include TimestampGenerator - # Create a new timestamp, as a 64-bit integer. This is just a wrapper around Time::now. - # - # @return [Integer] an integer representing a timestamp in microseconds. - def next - # Use Time.now, which has microsecond precision on MRI (and probably Rubinius) to make an int representing - # client timestamp in protocol requests. - timestamp = ::Time.now - timestamp.tv_sec * 1000000 + timestamp.tv_usec - end + # Create a new timestamp, as a 64-bit integer. This is just a wrapper around Time::now. + # + # @return [Integer] an integer representing a timestamp in microseconds. + def next + # Use Time.now, which has microsecond precision on MRI (and probably Rubinius) to make an int representing + # client timestamp in protocol requests. + timestamp = ::Time.now + timestamp.tv_sec * 1000000 + timestamp.tv_usec end end + end end diff --git a/spec/cassandra/cluster/options_spec.rb b/spec/cassandra/cluster/options_spec.rb index cc1f6a5cc..dbd91540b 100644 --- a/spec/cassandra/cluster/options_spec.rb +++ b/spec/cassandra/cluster/options_spec.rb @@ -26,7 +26,7 @@ def make_options(logger, Cassandra::Cluster::Options.new( logger, protocol_version, nil, nil, nil, nil, nil, false, connections_per_local_node, connections_per_remote_node, 60, 30, true, 1, 10, - true, true, requests_per_connection) + true, requests_per_connection) end module Cassandra From 907e47c056f5f78c2e5fb8f62f73475a242df2c9 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Wed, 4 May 2016 06:14:46 -0700 Subject: [PATCH 062/196] RUBY-214 - JRuby timestamps have millisecond precision and can cause client timestamp collisions. * fixed broken cucumber test. --- features/basics/client_side_timestamps.feature | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/features/basics/client_side_timestamps.feature b/features/basics/client_side_timestamps.feature index 70a66a1f4..4b72cf83f 100644 --- a/features/basics/client_side_timestamps.feature +++ b/features/basics/client_side_timestamps.feature @@ -23,7 +23,15 @@ Feature: Client-side Timestamps require 'cassandra' require 'delorean' - cluster = Cassandra.cluster(client_timestamps: true) + # Enable client-side timestamps. Valid values: + # nil / false : don't enable client-side timestamps. This is the default. + # true : enable client-side timestamps and use the default timestamp generator for the Ruby flavor being used: + # JRuby - Cassandra::TimestampGenerator::TickingOnDuplicate + # MRI/Rubinius - Cassandra::TimestampGenerator::Simple + # :simple : enable client-side timestamps and use a Cassandra::TimestampGenerator::Simple generator. + # :monotonic : enable client-side timestamps and use a Cassandra::TimestampGenerator::TickingOnDuplicate generator. + + cluster = Cassandra.cluster(client_timestamps: :simple) session = cluster.connect("simplex") # Insert in the present From 62c1250d9e0c405f2cb6c844509b1750678f8d36 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Wed, 4 May 2016 12:01:42 -0700 Subject: [PATCH 063/196] RUBY-207 - NoMethodError when handling write-timeout error --- CHANGELOG.md | 1 + lib/cassandra/cluster/client.rb | 2 +- spec/cassandra/cluster/client_spec.rb | 25 +++++++++++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb51fbb0e..7feb04a7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ Features: * Expand :client_timestamps cluster configuration option to allow user to specify his own generator for client timestamps. Bug Fixes: +* [RUBY-207](https://datastax-oss.atlassian.net/browse/RUBY-207) Get NoMethodError when handling a write-timeout error using a downgrading consistency retry policy. * [RUBY-214](https://datastax-oss.atlassian.net/browse/RUBY-214) Client timestamps in JRuby are not fine-grained enough, causing timestamp collisions and lost rows in C*. # 3.0.0 rc2 diff --git a/lib/cassandra/cluster/client.rb b/lib/cassandra/cluster/client.rb index 95be7c3bb..30f982d66 100644 --- a/lib/cassandra/cluster/client.rb +++ b/lib/cassandra/cluster/client.rb @@ -1358,7 +1358,7 @@ def handle_response(response_future, promise.fulfill( Results::Void.new(r.custom_payload, r.warnings, - r.trace_id, + nil, keyspace, statement, options, diff --git a/spec/cassandra/cluster/client_spec.rb b/spec/cassandra/cluster/client_spec.rb index 133b438c9..ca797d237 100644 --- a/spec/cassandra/cluster/client_spec.rb +++ b/spec/cassandra/cluster/client_spec.rb @@ -733,6 +733,31 @@ class Cluster end.to raise_error(Cassandra::Errors::InvalidError, 'blargh') end + it 'returns a Void result when there is a write-timeout with downgrading-consistency policy' do + driver_settings[:retry_policy] = Retry::Policies::DowngradingConsistency.new + io_reactor.on_connection do |connection| + connection.handle_request do |request| + case request + when Cassandra::Protocol::OptionsRequest + Cassandra::Protocol::SupportedResponse.new({}) + when Cassandra::Protocol::StartupRequest + Cassandra::Protocol::ReadyResponse.new + when Cassandra::Protocol::PrepareRequest + Protocol::PreparedResultResponse.new(nil, nil, 123, [], [], nil, nil) + when Cassandra::Protocol::ExecuteRequest + Protocol::WriteTimeoutErrorResponse.new(nil, nil, 123, 'write timeout', :one, 'x', 'y', 'simple') + end + end + end + + client.connect.value + + statement = client.prepare('SELECT * FROM songs', Execution::Options.new(:consistency => :one)).get + + expect(client.execute(statement.bind, Execution::Options.new(:consistency => :one)).get) + .to be_instance_of(Results::Void) + end + it 'raises if all hosts failed' do io_reactor.on_connection do |connection| connection.handle_request do |request| From 1f26a3dd7b66dfb91ba4bf9ff8631f671326c920 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Wed, 4 May 2016 15:10:30 -0700 Subject: [PATCH 064/196] RUBY-211 - Default max-remote-hosts-to-use for DC Aware Round Robin load-balancing policy to 0 instead of nil, thus defaulting to not use remote nodes. --- CHANGELOG.md | 7 +++++++ .../load_balancing/policies/dc_aware_round_robin.rb | 9 ++++++++- .../policies/dc_aware_round_robin_spec.rb | 12 +++++++++++- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb51fbb0e..208a96b94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ Features: Bug Fixes: * [RUBY-214](https://datastax-oss.atlassian.net/browse/RUBY-214) Client timestamps in JRuby are not fine-grained enough, causing timestamp collisions and lost rows in C*. +Breaking Changes: + +* The Datacenter-aware load balancing policy (Cassandra::LoadBalancing::Policies::DCAwareRoundRobin) defaults to using + nodes in the local DC only. In prior releases, the policy would fall back to remote nodes after exhausting local nodes. + Specify a positive value for `max_remote_hosts_to_use` when initializing the policy to allow remote node use. + + # 3.0.0 rc2 Features: * Add protocol_version configuration option to allow the user to force the protocol version to use for communication with nodes. diff --git a/lib/cassandra/load_balancing/policies/dc_aware_round_robin.rb b/lib/cassandra/load_balancing/policies/dc_aware_round_robin.rb index 1d15efe73..139930571 100644 --- a/lib/cassandra/load_balancing/policies/dc_aware_round_robin.rb +++ b/lib/cassandra/load_balancing/policies/dc_aware_round_robin.rb @@ -62,7 +62,7 @@ def next include MonitorMixin def initialize(datacenter = nil, - max_remote_hosts_to_use = nil, + max_remote_hosts_to_use = 0, use_remote_hosts_for_local_consistency = false) datacenter &&= String(datacenter) max_remote_hosts_to_use &&= Integer(max_remote_hosts_to_use) @@ -75,6 +75,13 @@ def initialize(datacenter = nil, end end + # If use_remote* is true, max_remote* must be > 0 + if use_remote_hosts_for_local_consistency + Util.assert(max_remote_hosts_to_use.nil? || max_remote_hosts_to_use > 0, + 'max_remote_hosts_to_use must be nil (meaning unlimited) or > 0 when ' \ + 'use_remote_hosts_for_local_consistency is true') + end + @datacenter = datacenter @max_remote = max_remote_hosts_to_use @local = ::Array.new diff --git a/spec/cassandra/load_balancing/policies/dc_aware_round_robin_spec.rb b/spec/cassandra/load_balancing/policies/dc_aware_round_robin_spec.rb index 6962f48e9..e6edca128 100644 --- a/spec/cassandra/load_balancing/policies/dc_aware_round_robin_spec.rb +++ b/spec/cassandra/load_balancing/policies/dc_aware_round_robin_spec.rb @@ -23,7 +23,7 @@ module LoadBalancing module Policies describe(DCAwareRoundRobin) do let(:datacenter) { 'DC1' } - let(:max_remote_hosts_to_use) { nil } + let(:max_remote_hosts_to_use) { 0 } let(:use_remote_hosts_for_local_consistency) { false } let(:policy) { DCAwareRoundRobin.new(datacenter, max_remote_hosts_to_use, use_remote_hosts_for_local_consistency) } @@ -34,6 +34,15 @@ module Policies let(:distance) { policy.distance(host) } + describe '#constructor' do + let(:use_remote_hosts_for_local_consistency) { true } + it 'should error out if use_remmote_hosts_for_local_consistency is true and max_remote_hosts_to_use is 0' do + expect { + policy + }.to raise_error(ArgumentError) + end + end + describe('#host_up') do before do policy.host_up(host) @@ -49,6 +58,7 @@ module Policies end context('and another host in remote datacenter is up') do + let(:max_remote_hosts_to_use) { nil } let(:another_host) { Host.new('127.0.0.2', nil, nil, host_datacenter) } before do From c0fa388fb757ba7d49350af96012725ad1ee3390 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Thu, 5 May 2016 09:32:11 -0700 Subject: [PATCH 065/196] RUBY-211 - Default max-remote-hosts-to-use for DC Aware Round Robin load-balancing policy to 0 instead of nil, thus defaulting to not use remote nodes. * Fixed broken integration and cucumber tests. --- features/load_balancing/datacenter_aware.feature | 12 +++++++----- integration/load_balancing/round_robin_test.rb | 4 ++-- integration/load_balancing/token_aware_test.rb | 4 ++-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/features/load_balancing/datacenter_aware.feature b/features/load_balancing/datacenter_aware.feature index 25f268f89..6944e93f2 100644 --- a/features/load_balancing/datacenter_aware.feature +++ b/features/load_balancing/datacenter_aware.feature @@ -6,9 +6,10 @@ Feature: Datacenter-aware Round Robin Policy datacenter if necessary. The name of the local datacenter must be supplied by the user. - All known remote hosts will be tried when local nodes are not available. - However, you can configure the exact number of remote hosts that will be used - by passing that number when constructing a policy instance. + By default, this policy will not actually fall back to nodes of a remote datacenter. + You must configure the exact number of remote hosts that will be used + by passing that number when constructing a policy instance. A nil value means there + is no limit on remote hosts to be potentially used. By default, this policy will not attempt to use remote hosts for local consistencies (`:local_one` or `:local_quorum`), however, it is possible to @@ -107,7 +108,8 @@ Feature: Datacenter-aware Round Robin Policy require 'cassandra' datacenter = "dc2" - policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new(datacenter) + # NOTE: second arg to policy constructor, indicating any number of remote nodes may be used. + policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new(datacenter, nil) cluster = Cassandra.cluster(consistency: :one, load_balancing_policy: policy) session = cluster.connect('simplex') @@ -159,7 +161,7 @@ Feature: Datacenter-aware Round Robin Policy require 'cassandra' datacenter = "dc2" - policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new(datacenter) + policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new(datacenter, nil) cluster = Cassandra.cluster(consistency: :one, load_balancing_policy: policy) session = cluster.connect('simplex') diff --git a/integration/load_balancing/round_robin_test.rb b/integration/load_balancing/round_robin_test.rb index 275e1bb4c..43a4b362c 100644 --- a/integration/load_balancing/round_robin_test.rb +++ b/integration/load_balancing/round_robin_test.rb @@ -118,7 +118,7 @@ def test_dc_aware_round_robin_queries_to_local_dc def test_dc_aware_round_robin_queries_to_remote_dc_if_local_down setup_schema datacenter = "dc2" - policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new(datacenter) + policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new(datacenter, nil) cluster = Cassandra.cluster(consistency: :one, load_balancing_policy: policy) session = cluster.connect("simplex") @@ -138,7 +138,7 @@ def test_dc_aware_round_robin_queries_to_remote_dc_if_local_down def test_raise_error_on_dc_aware_round_robin_unable_to_query_to_required_dc setup_schema datacenter = "dc1" - policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new(datacenter) + policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new(datacenter, nil) cluster = Cassandra.cluster(consistency: :one, load_balancing_policy: policy) session = cluster.connect("simplex") diff --git a/integration/load_balancing/token_aware_test.rb b/integration/load_balancing/token_aware_test.rb index b10ae486a..64d9efc86 100644 --- a/integration/load_balancing/token_aware_test.rb +++ b/integration/load_balancing/token_aware_test.rb @@ -87,7 +87,7 @@ def test_token_aware_routes_to_primary_replica def test_token_aware_routes_to_next_replica_if_primary_down setup_schema - base_policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new('dc1') + base_policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new('dc1', nil) policy = Cassandra::LoadBalancing::Policies::TokenAware.new(base_policy) cluster = Cassandra.cluster(consistency: :one, load_balancing_policy: policy) session = cluster.connect("simplex") @@ -169,7 +169,7 @@ def test_token_aware_routes_to_primary_replica_in_primary_dc def test_token_aware_routes_to_secondary_replica_if_primary_dc_down setup_schema datacenter = "dc2" - base_policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new(datacenter) + base_policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new(datacenter, nil) policy = Cassandra::LoadBalancing::Policies::TokenAware.new(base_policy) cluster = Cassandra.cluster(consistency: :one, load_balancing_policy: policy) session = cluster.connect("simplex") From 1da4ec0a56c4a6035750f04b083ebdae0909210f Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Tue, 10 May 2016 09:52:13 -0700 Subject: [PATCH 066/196] RUBY-175 - escape reserved words * Also properly escape names that contain double-quotes. --- lib/cassandra/util.rb | 5 ++++- spec/cassandra/table_spec.rb | 8 ++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/cassandra/util.rb b/lib/cassandra/util.rb index 66c855ddb..b07f99a2a 100644 --- a/lib/cassandra/util.rb +++ b/lib/cassandra/util.rb @@ -165,7 +165,10 @@ def escape_name(name) # If name only contains lower-case chars and it's not a reserved word, return it # as-is. Otherwise, quote. return name if name[LOWERCASE_REGEXP] == name && !RESERVED_WORDS.include?(name) - DBL_QUOT + name + DBL_QUOT + + # Replace double-quotes within name with two double-quotes (if any) and surround the whole + # thing with double-quotes + DBL_QUOT + name.gsub('"', '""') + DBL_QUOT end def guess_type(object) diff --git a/spec/cassandra/table_spec.rb b/spec/cassandra/table_spec.rb index bf05ecb0b..6b7d69fc9 100644 --- a/spec/cassandra/table_spec.rb +++ b/spec/cassandra/table_spec.rb @@ -27,6 +27,7 @@ module Cassandra let(:col) { double('col')} let(:col2) { double('col2')} let(:col3) { double('from')} + let(:quote_column) { double('"my"col"')} let(:options) { double('options')} let(:id) { 1234 } before do @@ -37,16 +38,19 @@ module Cassandra allow(col2).to receive(:type).and_return(int) allow(col3).to receive(:name).and_return('from') allow(col3).to receive(:type).and_return(varchar) + allow(quote_column).to receive(:name).and_return('"my"col"') + allow(quote_column).to receive(:type).and_return(varchar) allow(options).to receive(:to_cql).and_return('opt1=value1') end it 'should quote keyspace, table, columns properly' do - t = Table.new(ks, 'mytable1', [col], [], [col2, col3], options, [], id) + t = Table.new(ks, 'mytable1', [col], [], [col2, col3, quote_column], options, [], id) expected_cql = <<-EOF CREATE TABLE "myks1"."mytable1" ( col int PRIMARY KEY, "col2" int, - "from" text + "from" text, + """my""col""" text ) WITH opt1=value1; EOF From 6525437181dfcb21608098c21442133935a0aec1 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Tue, 10 May 2016 11:29:53 -0700 Subject: [PATCH 067/196] RUBY-162 - cluster.inspect should return more useful info --- lib/cassandra/cluster.rb | 5 +++++ lib/cassandra/cluster/metadata.rb | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/cassandra/cluster.rb b/lib/cassandra/cluster.rb index f99d7bd02..0868f1b41 100644 --- a/lib/cassandra/cluster.rb +++ b/lib/cassandra/cluster.rb @@ -280,6 +280,11 @@ def close def inspect "#<#{self.class.name}:0x#{object_id.to_s(16)} " \ "name=#{name.inspect}, " \ + "port=#{@connection_options.port}, " \ + "protocol_version=#{@connection_options.protocol_version}, " \ + "load_balancing_policy=#{@load_balancing_policy.inspect}, " \ + "consistency=#{@execution_options.consistency.inspect}, " \ + "timeout=#{@execution_options.timeout.inspect}, " \ "hosts=#{hosts.inspect}, " \ "keyspaces=#{keyspaces.inspect}>" end diff --git a/lib/cassandra/cluster/metadata.rb b/lib/cassandra/cluster/metadata.rb index 8db783583..acc4b8975 100644 --- a/lib/cassandra/cluster/metadata.rb +++ b/lib/cassandra/cluster/metadata.rb @@ -61,7 +61,7 @@ def find_replicas(keyspace, statement) end def update(data) - @name = data['name'] + @name = data['cluster_name'] @partitioner = @partitioners[data['partitioner']] self From da5df0d491b03ee0f8dd471df75eb48e5f05b8a0 Mon Sep 17 00:00:00 2001 From: Kishan Karunaratne Date: Tue, 10 May 2016 13:23:29 -0700 Subject: [PATCH 068/196] [RUBY-175] Integration tests for quoted identifiers --- integration/datatype_utils.rb | 133 ++++++++++++++++++++++++++++++++++ integration/metadata_test.rb | 70 ++++++++++++++++++ 2 files changed, 203 insertions(+) diff --git a/integration/datatype_utils.rb b/integration/datatype_utils.rb index 379b79acf..9de040591 100644 --- a/integration/datatype_utils.rb +++ b/integration/datatype_utils.rb @@ -98,4 +98,137 @@ def self.get_collection_sample(complex_type, datatype) end end + def self.reserved_words + @@reserved_words ||= begin + reserved_words = Array.new(%w( + add + aggregate + all + allow + alter + and + apply + as + asc + ascii + authorize + batch + begin + bigint + blob + boolean + by + called + clustering + columnfamily + compact + contains + count + counter + create + custom + date + decimal + delete + desc + describe + distinct + double + drop + entries + execute + exists + filtering + finalfunc + float + from + frozen + full + function + functions + grant + if + in + index + inet + infinity + initcond + input + insert + int + into + is + json + key + keys + keyspace + keyspaces + language + limit + list + login + map + materialized + modify + nan + nologin + norecursive + nosuperuser + not + null + of + on + options + or + order + password + permission + permissions + primary + rename + replace + returns + revoke + role + roles + schema + select + set + sfunc + smallint + static + storage + stype + superuser + table + text + time + timestamp + timeuuid + tinyint + to + token + trigger + truncate + ttl + tuple + type + unlogged + update + use + user + users + using + uuid + values + varchar + varint + view + where + with + writetime + )) + end + end + end diff --git a/integration/metadata_test.rb b/integration/metadata_test.rb index 2375f2434..7ad2701e2 100644 --- a/integration/metadata_test.rb +++ b/integration/metadata_test.rb @@ -17,6 +17,7 @@ #++ require File.dirname(__FILE__) + '/integration_test_case.rb' +require File.dirname(__FILE__) + '/datatype_utils.rb' class MetadataTest < IntegrationTestCase @@ -133,6 +134,75 @@ def test_column_ordering_is_deterministic end end + # Test for retrieving table metadata with quoted identifiers + # + # test_can_retrieve_quoted_table_metadata tests that all pieces of table metadata can be retrieved, when the + # table has quoted identifiers. It goes through each piece of table view metadata and verifies that each piece + # is as expected. + # + # @since 3.0.0 + # @jira_ticket RUBY-175 + # @expected_result table metadata with quoted identifiers should be retrieved. + # + # @test_category metadata + # + def test_can_retrieve_quoted_table_metadata + # Check upper-case chars, unescaped upper-case chars, quoted numbers, quoted identifier with single quote, + # quoted identifier with double quotes + @session.execute("CREATE TABLE roles (\"FOO\" text, BAR ascii, \"10\" int, \"'20'\" int, \"\"\"30\"\"\" int, + \"f00\"\"b4r\" text, PRIMARY KEY (\"FOO\", BAR, \"10\", \"'20'\", \"\"\"30\"\"\", \"f00\"\"b4r\"))") + @listener.wait_for_table('simplex', 'roles') + + assert @cluster.keyspace('simplex').has_table?('roles') + table_meta = @cluster.keyspace('simplex').table('roles') + + assert_equal 'roles', table_meta.name + assert_equal 'simplex', table_meta.keyspace.name + assert_empty table_meta.indexes + refute_nil table_meta.id unless CCM.cassandra_version < '3.0.0' + refute_nil table_meta.options + + assert_columns([['FOO', :text], ['bar', :ascii], ['10', :int], ["'20'", :int], ["\"30\"", :int], ["f00\"b4r", :text]], + table_meta.primary_key) + assert_columns([['FOO', :text]], table_meta.partition_key) + assert_columns([['bar', :ascii], ['10', :int], ["'20'", :int], ["\"30\"", :int], ["f00\"b4r", :text]], + table_meta.clustering_columns) + assert_equal :asc, table_meta.clustering_order.first + + assert_equal 6, table_meta.columns.size + table_meta.each_column do |column| + assert ['FOO', 'bar', '10', "'20'", "\"30\"", "f00\"b4r"].any? { |name| name == column.name } + assert [:text, :ascii, :int].any? { |type| type == column.type.kind } + end + + table_cql = Regexp.new(/CREATE TABLE simplex\."roles" \( + "FOO" text, + bar ascii, + "10" int, + "'20'" int, + """30""" int, + "f00""b4r" text, + PRIMARY KEY \("FOO", bar, "10", "'20'", """30""", "f00""b4r"\) +\)/) + + assert_equal 0, table_meta.to_cql =~ table_cql + @session.execute("DROP TABLE roles") + + # Check all the reserved words + reserved_word_int_list = ["zz int PRIMARY KEY"] + DatatypeUtils.reserved_words.each do |word| + reserved_word_int_list.push("\"#{word}\" int") + end + + @session.execute("CREATE TABLE reserved_words (#{reserved_word_int_list.join(',')})") + @listener.wait_for_table('simplex', 'reserved_words') + + assert @cluster.keyspace('simplex').has_table?('reserved_words') + table_meta = @cluster.keyspace('simplex').table('reserved_words') + refute_nil table_meta.to_cql + @session.execute("DROP TABLE reserved_words") + end + # Test for skipping internal columns in static-compact tables # # test_skip_internal_columns_for_static_compact_table tests that the "column1 text" clustering From ec46b91753623598bf97414f75aa481b781693ea Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Tue, 10 May 2016 13:24:21 -0700 Subject: [PATCH 069/196] RUBY-162 - cluster.inspect should return more useful info * Fixed a flakey test. --- integration/session_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integration/session_test.rb b/integration/session_test.rb index db2aef588..3b1f3c9d4 100644 --- a/integration/session_test.rb +++ b/integration/session_test.rb @@ -637,11 +637,11 @@ def test_raise_error_on_future_resolution_timeout @@ccm_cluster.block_node("node1") future = session.execute_async("SELECT * FROM users") - start_time = Time.now.to_i + start_time = Time.now assert_raises(Cassandra::Errors::TimeoutError) do future.get(2) end - assert_equal 2, Time.now.to_i - start_time + assert_in_delta(2, Time.now - start_time, 0.5) ensure @@ccm_cluster.unblock_nodes cluster && cluster.close From 170cf2e8f96d028f89e0799a8510f4d50bcfe5c2 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Tue, 10 May 2016 16:01:17 -0700 Subject: [PATCH 070/196] RUBY-162 - cluster.inspect should return more useful info * Added .inspect methods for load-balancing policies. --- .../load_balancing/policies/dc_aware_round_robin.rb | 11 +++++++++++ lib/cassandra/load_balancing/policies/round_robin.rb | 7 +++++++ lib/cassandra/load_balancing/policies/token_aware.rb | 7 +++++++ lib/cassandra/load_balancing/policies/white_list.rb | 7 +++++++ 4 files changed, 32 insertions(+) diff --git a/lib/cassandra/load_balancing/policies/dc_aware_round_robin.rb b/lib/cassandra/load_balancing/policies/dc_aware_round_robin.rb index 139930571..ced77b50e 100644 --- a/lib/cassandra/load_balancing/policies/dc_aware_round_robin.rb +++ b/lib/cassandra/load_balancing/policies/dc_aware_round_robin.rb @@ -155,6 +155,17 @@ def plan(keyspace, statement, options) Plan.new(local, remote, position) end + + # @private + def inspect + "#<#{self.class.name}:0x#{object_id.to_s(16)} " \ + "datacenter=#{@datacenter.inspect}, " \ + "use_remote=#{@use_remote.inspect}, " \ + "max_remote=#{@max_remote.inspect}, " \ + "local=#{@local.inspect}, " \ + "remote=#{@remote.inspect}, " \ + "position=#{@position.inspect}>" + end end end end diff --git a/lib/cassandra/load_balancing/policies/round_robin.rb b/lib/cassandra/load_balancing/policies/round_robin.rb index 8f2c83c04..8b3d2564a 100644 --- a/lib/cassandra/load_balancing/policies/round_robin.rb +++ b/lib/cassandra/load_balancing/policies/round_robin.rb @@ -128,6 +128,13 @@ def plan(keyspace, statement, options) Plan.new(hosts, position) end + + # @private + def inspect + "#<#{self.class.name}:0x#{object_id.to_s(16)} " \ + "hosts=#{@hosts.inspect}, " \ + "position=#{@position.inspect}>" + end end end end diff --git a/lib/cassandra/load_balancing/policies/token_aware.rb b/lib/cassandra/load_balancing/policies/token_aware.rb index 1bf1ebb57..20e7ba52c 100644 --- a/lib/cassandra/load_balancing/policies/token_aware.rb +++ b/lib/cassandra/load_balancing/policies/token_aware.rb @@ -136,6 +136,13 @@ def plan(keyspace, statement, options) Plan.new(replicas, @policy, keyspace, statement, options) end + + # @private + def inspect + "#<#{self.class.name}:0x#{object_id.to_s(16)} " \ + "policy=#{@policy.inspect}, " \ + "shuffle=#{@shuffle.inspect}>" + end end end end diff --git a/lib/cassandra/load_balancing/policies/white_list.rb b/lib/cassandra/load_balancing/policies/white_list.rb index fffd5b530..c1ea8cc73 100644 --- a/lib/cassandra/load_balancing/policies/white_list.rb +++ b/lib/cassandra/load_balancing/policies/white_list.rb @@ -87,6 +87,13 @@ def host_up(host) def host_down(host) @policy.host_down(host) if @ips.include?(host.ip) end + + # @private + def inspect + "#<#{self.class.name}:0x#{object_id.to_s(16)} " \ + "policy=#{@policy.inspect}, " \ + "ips=#{@ips.inspect}>" + end end end end From c718ab64d855415b0168c7b6a6c7cd0ff875cd65 Mon Sep 17 00:00:00 2001 From: Kishan Karunaratne Date: Wed, 11 May 2016 13:46:16 -0700 Subject: [PATCH 071/196] [RUBY-162] Integration tests for cluster, session inspect --- integration/session_test.rb | 40 +++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/integration/session_test.rb b/integration/session_test.rb index 3b1f3c9d4..54bf1c1e2 100644 --- a/integration/session_test.rb +++ b/integration/session_test.rb @@ -830,4 +830,44 @@ def test_can_set_protocol_version_explicitly cluster && cluster.close end + # Test for cluster and session object inspection + # + # test_cluster_session_inspect tests that cluster and session objects can be inspected, and their stored options can + # be retrieved in the inspect string. It first creates a simple cluster and session object with some options defined. + # It then inspects these objects and verifies that the options set are visible in the inspect string. + # + # @since 3.0.0 + # @jira_ticket RUBY-162 + # @expected_result cluster and object options should be visible in the inspect string + # + # @test_category connection + # + def test_cluster_session_inspect + cluster = Cassandra.cluster(hosts: ['127.0.0.1'], consistency: :quorum, page_size: 10) + session = cluster.connect('simplex') + + cluster_inspect = cluster.inspect + assert_match(/name="ruby-driver-.*"/, cluster_inspect) + assert_match(/hosts=\[.* @ip=127\.0\.0\.1>\]/, cluster_inspect) + assert_match(/port=9042/, cluster_inspect) + assert_match(/protocol_version=[1-4]/, cluster_inspect) + assert_match(/keyspaces=\[.* @name=simplex>\]/, cluster_inspect) + assert_match(/load_balancing_policy=# Date: Thu, 12 May 2016 14:01:51 -0700 Subject: [PATCH 072/196] [RUBY-211] Integration tests and cucumber features for DCAwareRoundRobin lbp --- .../load_balancing/datacenter_aware.feature | 44 +-- features/load_balancing/token_aware.feature | 80 +++-- .../load_balancing/round_robin_test.rb | 276 ++++++++++++------ .../load_balancing/token_aware_test.rb | 149 ++++++---- 4 files changed, 369 insertions(+), 180 deletions(-) diff --git a/features/load_balancing/datacenter_aware.feature b/features/load_balancing/datacenter_aware.feature index 6944e93f2..2758e4bd6 100644 --- a/features/load_balancing/datacenter_aware.feature +++ b/features/load_balancing/datacenter_aware.feature @@ -8,12 +8,12 @@ Feature: Datacenter-aware Round Robin Policy By default, this policy will not actually fall back to nodes of a remote datacenter. You must configure the exact number of remote hosts that will be used - by passing that number when constructing a policy instance. A nil value means there - is no limit on remote hosts to be potentially used. + by passing that number when constructing a policy instance. A nil value means an + unlimited number of remote hosts can be potentially used. By default, this policy will not attempt to use remote hosts for local consistencies (`:local_one` or `:local_quorum`), however, it is possible to - change that behavior via constructor. + change that behavior via the constructor. Background: Given a running cassandra cluster in 2 datacenters with 2 nodes in each @@ -85,7 +85,7 @@ Feature: Datacenter-aware Round Robin Policy datacenter = "dc2" policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new(datacenter) - cluster = Cassandra.cluster(load_balancing_policy: policy) + cluster = Cassandra.cluster(load_balancing_policy: policy, consistency: :one) session = cluster.connect('simplex') hosts_used = 4.times.map do @@ -107,11 +107,11 @@ Feature: Datacenter-aware Round Robin Policy """ruby require 'cassandra' - datacenter = "dc2" - # NOTE: second arg to policy constructor, indicating any number of remote nodes may be used. - policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new(datacenter, nil) - cluster = Cassandra.cluster(consistency: :one, load_balancing_policy: policy) - session = cluster.connect('simplex') + datacenter = "dc2" + remotes_to_try = nil + policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new(datacenter, remotes_to_try) + cluster = Cassandra.cluster(load_balancing_policy: policy, consistency: :one) + session = cluster.connect('simplex') hosts_used = 4.times.map do info = session.execute("SELECT * FROM songs").execution_info @@ -137,7 +137,7 @@ Feature: Datacenter-aware Round Robin Policy datacenter = "dc2" remotes_to_try = 1 policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new(datacenter, remotes_to_try) - cluster = Cassandra.cluster(consistency: :one, load_balancing_policy: policy) + cluster = Cassandra.cluster(load_balancing_policy: policy, consistency: :one) session = cluster.connect('simplex') hosts_used = 4.times.map do @@ -155,15 +155,16 @@ Feature: Datacenter-aware Round Robin Policy Used 1 host, with ip 127\.0\.0\.(1|2) """ - Scenario: Requests with local consistencies are not routed to remote datacenters + Scenario: Requests with local consistencies are not routed to remote datacenters by default Given the following example: """ruby require 'cassandra' - datacenter = "dc2" - policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new(datacenter, nil) - cluster = Cassandra.cluster(consistency: :one, load_balancing_policy: policy) - session = cluster.connect('simplex') + datacenter = "dc2" + remotes_to_try = nil + policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new(datacenter, remotes_to_try) + cluster = Cassandra.cluster(load_balancing_policy: policy, consistency: :one) + session = cluster.connect('simplex') begin session.execute("SELECT * FROM songs", consistency: :local_one) @@ -185,14 +186,15 @@ Feature: Datacenter-aware Round Robin Policy """ruby require 'cassandra' - datacenter = "dc2" - use_remote = true - policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new(datacenter, nil, use_remote) - cluster = Cassandra.cluster(consistency: :one, load_balancing_policy: policy) - session = cluster.connect('simplex') + datacenter = "dc2" + remotes_to_try = nil + use_remote = true + policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new(datacenter, remotes_to_try, use_remote) + cluster = Cassandra.cluster(load_balancing_policy: policy) + session = cluster.connect('simplex') hosts_used = 4.times.map do - info = session.execute("SELECT * FROM songs").execution_info + info = session.execute("SELECT * FROM songs", consistency: :local_one).execution_info info.hosts.last.ip end.sort.uniq diff --git a/features/load_balancing/token_aware.feature b/features/load_balancing/token_aware.feature index 6b8aca749..8931a3419 100644 --- a/features/load_balancing/token_aware.feature +++ b/features/load_balancing/token_aware.feature @@ -8,7 +8,7 @@ Feature: Token-aware Load Balancing Policy the right partitioners and replication strategies for a given keyspace and locate replicas for a given statement. - In case replica node(s) cannot be found or reached, this policy fallsback + In case replica node(s) cannot be found or reached, this policy falls back onto the wrapped policy plan. Background: @@ -95,11 +95,11 @@ Feature: Token-aware Load Balancing Policy """ruby require 'cassandra' - policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new - policy = Cassandra::LoadBalancing::Policies::TokenAware.new(policy) - cluster = Cassandra.cluster(load_balancing_policy: policy) - session = cluster.connect('simplex') - statement = session.prepare("SELECT token(id) FROM songs WHERE id = ?") + base_policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new('dc2') + policy = Cassandra::LoadBalancing::Policies::TokenAware.new(base_policy) + cluster = Cassandra.cluster(load_balancing_policy: policy) + session = cluster.connect('simplex') + statement = session.prepare("SELECT token(id) FROM songs WHERE id = ?") [ Cassandra::Uuid.new('756716f7-2e54-4715-9f00-91dcbea6cf50'), @@ -115,9 +115,9 @@ Feature: Token-aware Load Balancing Policy When it is executed Then its output should contain: """ - uuid=756716f7-2e54-4715-9f00-91dcbea6cf50 token=-4565826248849633211 replica=127.0.0.2 total=1 - uuid=f6071e72-48ec-4fcb-bf3e-379c8a696488 token=-1176857621403111796 replica=127.0.0.2 total=1 - uuid=fbdf82ed-0063-4796-9c7c-a3d4f47b4b25 token=2440231132048646025 replica=127.0.0.1 total=1 + uuid=756716f7-2e54-4715-9f00-91dcbea6cf50 token=-4565826248849633211 replica=127.0.0.4 total=1 + uuid=f6071e72-48ec-4fcb-bf3e-379c8a696488 token=-1176857621403111796 replica=127.0.0.4 total=1 + uuid=fbdf82ed-0063-4796-9c7c-a3d4f47b4b25 token=2440231132048646025 replica=127.0.0.3 total=1 """ Scenario: Requests are routed according to wrapped policy plan when primary replica is down @@ -125,13 +125,14 @@ Feature: Token-aware Load Balancing Policy """ruby require 'cassandra' - policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new - policy = Cassandra::LoadBalancing::Policies::TokenAware.new(policy) - cluster = Cassandra.cluster(load_balancing_policy: policy) - session = cluster.connect('simplex') - statement = session.prepare("SELECT token(id) FROM songs WHERE id = ?") + base_policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new('dc2') + policy = Cassandra::LoadBalancing::Policies::TokenAware.new(base_policy) + cluster = Cassandra.cluster(load_balancing_policy: policy) + session = cluster.connect('simplex') + statement = session.prepare("SELECT token(id) FROM songs WHERE id = ?") [ + Cassandra::Uuid.new('756716f7-2e54-4715-9f00-91dcbea6cf50'), Cassandra::Uuid.new('f6071e72-48ec-4fcb-bf3e-379c8a696488'), Cassandra::Uuid.new('fbdf82ed-0063-4796-9c7c-a3d4f47b4b25') ].each do |uuid| @@ -141,12 +142,13 @@ Feature: Token-aware Load Balancing Policy puts "uuid=#{uuid} token=#{result.first.values.first} replica=#{replica.ip} total=#{total}" end """ - And node 2 is stopped + And node 4 is stopped When it is executed Then its output should contain: """ - uuid=f6071e72-48ec-4fcb-bf3e-379c8a696488 token=-1176857621403111796 replica=127.0.0.1 total=1 - uuid=fbdf82ed-0063-4796-9c7c-a3d4f47b4b25 token=2440231132048646025 replica=127.0.0.1 total=1 + uuid=756716f7-2e54-4715-9f00-91dcbea6cf50 token=-4565826248849633211 replica=127.0.0.3 total=1 + uuid=f6071e72-48ec-4fcb-bf3e-379c8a696488 token=-1176857621403111796 replica=127.0.0.3 total=1 + uuid=fbdf82ed-0063-4796-9c7c-a3d4f47b4b25 token=2440231132048646025 replica=127.0.0.3 total=1 """ Scenario: Requests with compound partition keys are routed to the primary replica @@ -154,7 +156,7 @@ Feature: Token-aware Load Balancing Policy """ruby require 'cassandra' - policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new + policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new('dc2') policy = Cassandra::LoadBalancing::Policies::TokenAware.new(policy) cluster = Cassandra.cluster(load_balancing_policy: policy) session = cluster.connect('simplex') @@ -175,8 +177,42 @@ Feature: Token-aware Load Balancing Policy When it is executed Then its output should contain: """ - uuid=2cc9ccb7-6221-4ccb-8387-f22b6a1b354d title=2cc9ccb7-6221-4ccb-8387-f22b6a1b354d token=6231549073425362204 replica=127.0.0.1 total=1 - uuid=2cc9ccb7-6221-4ccb-8387-f22b6a1b354d title=2cc9ccb7-6221-4ccb-8387-f22b6a1b354d token=-115815985718975675 replica=127.0.0.2 total=1 - uuid=3fd2bedf-a8c8-455a-a462-0cd3a4353c54 title=3fd2bedf-a8c8-455a-a462-0cd3a4353c54 token=-463065628644986368 replica=127.0.0.2 total=1 - uuid=3fd2bedf-a8c8-455a-a462-0cd3a4353c54 title=3fd2bedf-a8c8-455a-a462-0cd3a4353c54 token=-8087998491924709995 replica=127.0.0.2 total=1 + uuid=2cc9ccb7-6221-4ccb-8387-f22b6a1b354d title=2cc9ccb7-6221-4ccb-8387-f22b6a1b354d token=6231549073425362204 replica=127.0.0.3 total=1 + uuid=2cc9ccb7-6221-4ccb-8387-f22b6a1b354d title=2cc9ccb7-6221-4ccb-8387-f22b6a1b354d token=-115815985718975675 replica=127.0.0.4 total=1 + uuid=3fd2bedf-a8c8-455a-a462-0cd3a4353c54 title=3fd2bedf-a8c8-455a-a462-0cd3a4353c54 token=-463065628644986368 replica=127.0.0.4 total=1 + uuid=3fd2bedf-a8c8-455a-a462-0cd3a4353c54 title=3fd2bedf-a8c8-455a-a462-0cd3a4353c54 token=-8087998491924709995 replica=127.0.0.4 total=1 + """ + + Scenario: Requests with local consistencies are routed to a remote datacenter when primary replica is down + Given the following example: + """ruby + require 'cassandra' + + max_remote_hosts_to_use = nil + use_remote_hosts_for_local_consistency = true + base_policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new('dc2', + max_remote_hosts_to_use, + use_remote_hosts_for_local_consistency) + policy = Cassandra::LoadBalancing::Policies::TokenAware.new(base_policy) + cluster = Cassandra.cluster(load_balancing_policy: policy) + session = cluster.connect('simplex') + statement = session.prepare("SELECT token(id) FROM songs WHERE id = ?") + + [ + Cassandra::Uuid.new('756716f7-2e54-4715-9f00-91dcbea6cf50'), + Cassandra::Uuid.new('f6071e72-48ec-4fcb-bf3e-379c8a696488') + ].each do |uuid| + result = session.execute(statement, arguments: [uuid], consistency: :local_one) + replica = result.execution_info.hosts.first + total = result.execution_info.hosts.size + puts "uuid=#{uuid} token=#{result.first.values.first} replica=#{replica.ip} total=#{total}" + end + """ + And node 3 is stopped + And node 4 is stopped + When it is executed + Then its output should contain: """ + uuid=756716f7-2e54-4715-9f00-91dcbea6cf50 token=-4565826248849633211 replica=127.0.0.2 total=1 + uuid=f6071e72-48ec-4fcb-bf3e-379c8a696488 token=-1176857621403111796 replica=127.0.0.1 total=1 + """ \ No newline at end of file diff --git a/integration/load_balancing/round_robin_test.rb b/integration/load_balancing/round_robin_test.rb index 43a4b362c..cdb23ca78 100644 --- a/integration/load_balancing/round_robin_test.rb +++ b/integration/load_balancing/round_robin_test.rb @@ -34,27 +34,26 @@ def setup_schema # Test for basic round robin load balancing policy # - # test_round_robin_used_explicitly tests the round robin policy by explicitly using it - # during the cluster connection. 4 queries are made, and each query should go to a different - # node in the Cassandra cluster. - # - # @return [String] List of hosts used in the query execution. + # test_round_robin_used_explicitly tests the round robin policy by explicitly using it during the cluster connection. + # 4 queries are made, and each query should go to a different node in the Cassandra cluster. Since each query goes + # to a different Cassandra node, this test also verifies that the round robin policy ignores datacenters. # # @since 1.0.0 - # @expected_result Each of the 4 queries should be routed to a different node in the cluster. + # @expected_result Each of the 4 queries should be routed to a different node in the cluster, across datacenters. # - # @test_assumptions Existing Cassandra cluster with keyspace 'simplex' and table 'users'. + # @test_assumptions Existing Cassandra cluster with two datacenters, keyspace 'simplex', and table 'users'. # @test_category load_balancing:round_robin # - def test_round_robin_used_explicitly + def test_round_robin_used_explicitly_and_ignores_datacenters setup_schema policy = Cassandra::LoadBalancing::Policies::RoundRobin.new - cluster = Cassandra.cluster(consistency: :one, load_balancing_policy: policy) - session = cluster.connect("simplex") + cluster = Cassandra.cluster(load_balancing_policy: policy) + session = cluster.connect('simplex') hosts_used = [] 4.times do info = session.execute("INSERT INTO users (user_id, first, last, age) VALUES (0, 'John', 'Doe', 40)").execution_info + assert_equal 1, info.hosts.size hosts_used.push(info.hosts.last.ip.to_s) end @@ -62,13 +61,24 @@ def test_round_robin_used_explicitly cluster.close end + # Test for whitelisted round robin load balancing policy + # + # test_whitelisted_round_robin tests the round robin policy using a whitelist. 4 queries are made, and each query + # should go to a whitelisted node in the Cassandra cluster. + # + # @since 1.0.0 + # @expected_result Each of the 4 queries should be routed to a whitelisted node in the cluster. + # + # @test_assumptions Existing Cassandra cluster with two datacenters, keyspace 'simplex', and table 'users'. + # @test_category load_balancing:round_robin + # def test_whitelisted_round_robin setup_schema - allowed_ips = ["127.0.0.1", "127.0.0.3"] + allowed_ips = ['127.0.0.1', '127.0.0.3'] round_robin = Cassandra::LoadBalancing::Policies::RoundRobin.new whitelist = Cassandra::LoadBalancing::Policies::WhiteList.new(allowed_ips, round_robin) - cluster = Cassandra.cluster(load_balancing_policy: whitelist, consistency: :one) - session = cluster.connect("simplex") + cluster = Cassandra.cluster(load_balancing_policy: whitelist) + session = cluster.connect('simplex') hosts_used = [] 4.times do @@ -81,49 +91,90 @@ def test_whitelisted_round_robin cluster.close end - def test_round_robin_ignores_datacenters + # Test for basic dc-aware round robin load balancing policy + # + # test_dc_aware_round_robin_queries_to_local_dc tests the round robin policy wrapping around a datacenter-aware + # policy. Using dc2 (node3, node4), 4 queries are made, and each query should go to a one of these two nodes in the + # Cassandra cluster. Note the default consistency is :local_one. + # + # @since 1.0.0 + # @expected_result Each of the 4 queries should be routed to a node in the local dc, dc2 (node3 or node4). + # + # @test_assumptions Existing Cassandra cluster with two datacenters, keyspace 'simplex', and table 'users'. + # @test_category load_balancing:round_robin + # + def test_dc_aware_round_robin_queries_to_local_dc setup_schema - policy = Cassandra::LoadBalancing::Policies::RoundRobin.new - cluster = Cassandra.cluster(load_balancing_policy: policy, consistency: :one) - session = cluster.connect("simplex") + datacenter = 'dc2' + policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new(datacenter) + cluster = Cassandra.cluster(load_balancing_policy: policy) + session = cluster.connect('simplex') hosts_used = [] 4.times do info = session.execute("INSERT INTO users (user_id, first, last, age) VALUES (0, 'John', 'Doe', 40)").execution_info - assert_equal 1, info.hosts.size hosts_used.push(info.hosts.last.ip.to_s) end - assert_equal ['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4'], hosts_used.sort + assert_equal ['127.0.0.3', '127.0.0.4'], hosts_used.sort.uniq cluster.close end - def test_dc_aware_round_robin_queries_to_local_dc + # Test for dc-aware round robin load balancing policy with local down + # + # test_dc_aware_round_robin_does_not_query_to_remote_dc_by_default_if_local_down tests the dc-aware round robin + # policy's behavior when the local dc is down. It performs a query and verifies that a NoHostsAvailable error is + # raised as no local nodes are available. Note the default consistency is :local_one. + # + # @expected_errors [Cassandra::Errors::NoHostsAvailable] When local dc is down. + # + # @since 1.0.0 + # @expected_result A NoHostsAvailable should be raised as the local dc is down, and no remotes are specified. + # + # @test_assumptions Existing Cassandra cluster with two datacenters, keyspace 'simplex', and table 'users'. + # @test_category load_balancing:round_robin + # + def test_dc_aware_round_robin_does_not_query_to_remote_dc_by_default_if_local_down setup_schema - datacenter = "dc1" + datacenter = 'dc1' policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new(datacenter) - cluster = Cassandra.cluster(load_balancing_policy: policy, consistency: :one) - session = cluster.connect("simplex") + cluster = Cassandra.cluster(load_balancing_policy: policy) + session = cluster.connect('simplex') - hosts_used = [] - 4.times do - info = session.execute("INSERT INTO users (user_id, first, last, age) VALUES (0, 'John', 'Doe', 40)").execution_info - hosts_used.push(info.hosts.last.ip.to_s) + @@ccm_cluster.stop_node('node1') + @@ccm_cluster.stop_node('node2') + + assert_raises(Cassandra::Errors::NoHostsAvailable) do + session.execute("INSERT INTO users (user_id, first, last, age) VALUES (0, 'John', 'Doe', 40)").execution_info end - assert_equal ['127.0.0.1', '127.0.0.2'], hosts_used.sort.uniq cluster.close end - def test_dc_aware_round_robin_queries_to_remote_dc_if_local_down + # Test for dc-aware round robin lbp with local down but remotes enabled + # + # test_dc_aware_round_robin_can_query_to_remote_dc_if_local_down tests the dc-aware round robin policy's behavior + # when the local dc is down, but remote hosts are enabled. It first specifies the load balancing policy with + # max_remote_hosts_to_use = nil to allow unlimited remote hosts to be used for non-local consistency queries. It then + # performs 4 queries and verifies that hosts from dc2 (node3, node4) are used. Note the consistency is :one to allow + # for remote hosts to be used for the query. + # + # @since 1.0.0 + # @expected_result Each of the 4 queries should be routed a different node in the remote dc, dc2 (node3 or node4). + # + # @test_assumptions Existing Cassandra cluster with two datacenters, keyspace 'simplex', and table 'users'. + # @test_category load_balancing:round_robin + # + def test_dc_aware_round_robin_can_query_to_remote_dc_if_local_down setup_schema - datacenter = "dc2" - policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new(datacenter, nil) - cluster = Cassandra.cluster(consistency: :one, load_balancing_policy: policy) - session = cluster.connect("simplex") + datacenter = 'dc1' + max_remote_hosts_to_use = nil + policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new(datacenter, max_remote_hosts_to_use) + cluster = Cassandra.cluster(load_balancing_policy: policy, consistency: :one) + session = cluster.connect('simplex') - @@ccm_cluster.stop_node('node3') - @@ccm_cluster.stop_node('node4') + @@ccm_cluster.stop_node('node1') + @@ccm_cluster.stop_node('node2') hosts_used = [] 4.times do @@ -131,42 +182,35 @@ def test_dc_aware_round_robin_queries_to_remote_dc_if_local_down hosts_used.push(info.hosts.last.ip.to_s) end - assert_equal ['127.0.0.1', '127.0.0.2'], hosts_used.sort.uniq + assert_equal ['127.0.0.3', '127.0.0.4'], hosts_used.sort.uniq cluster.close end - def test_raise_error_on_dc_aware_round_robin_unable_to_query_to_required_dc + # Test for dc-aware round robin lbp with local down but one remote enabled + # + # test_dc_aware_round_robin_routes_up_to_max_hosts_in_remote tests the dc-aware round robin policy's behavior + # when the local dc is down, but one remote host is enabled. It first specifies the load balancing policy with + # max_remote_hosts_to_use = 1 to allow up to 1 remote host to be used for non-local consistency queries. It then + # performs 4 queries and verifies that one host from dc2 (node3, node4) is used. Note the consistency is :one to allow + # for remote hosts to be used for the query. + # + # @since 1.0.0 + # @expected_result Each of the 4 queries should be routed one node in the remote dc, dc2 (node3 or node4). + # + # @test_assumptions Existing Cassandra cluster with two datacenters, keyspace 'simplex', and table 'users'. + # @test_category load_balancing:round_robin + # + def test_dc_aware_round_robin_routes_up_to_max_hosts_in_remote setup_schema - datacenter = "dc1" - policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new(datacenter, nil) - cluster = Cassandra.cluster(consistency: :one, load_balancing_policy: policy) - session = cluster.connect("simplex") + datacenter = 'dc1' + max_remote_hosts_to_use = 1 + policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new(datacenter, max_remote_hosts_to_use) + cluster = Cassandra.cluster(load_balancing_policy: policy, consistency: :one) + session = cluster.connect('simplex') @@ccm_cluster.stop_node('node1') @@ccm_cluster.stop_node('node2') - begin - Retry.with_attempts(5, Cassandra::Errors::WriteTimeoutError) { - session.execute("INSERT INTO users (user_id, first, last, age) VALUES (0, 'John', 'Doe', 40)") } - rescue Cassandra::Errors::NoHostsAvailable => e - raise e unless e.errors.first.last.is_a?(Cassandra::Errors::UnavailableError) - end - - @@ccm_cluster.start_node('node1') - cluster.close - end - - def test_dc_aware_round_robin_routes_up_to_max_hosts_in_remote - setup_schema - datacenter = "dc2" - remotes_to_try = 1 - policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new(datacenter, remotes_to_try) - cluster = Cassandra.cluster(consistency: :one, load_balancing_policy: policy) - session = cluster.connect("simplex") - - @@ccm_cluster.stop_node('node3') - @@ccm_cluster.stop_node('node4') - hosts_used = [] 4.times do info = session.execute("INSERT INTO users (user_id, first, last, age) VALUES (0, 'John', 'Doe', 40)").execution_info @@ -174,50 +218,111 @@ def test_dc_aware_round_robin_routes_up_to_max_hosts_in_remote end assert_equal 1, hosts_used.uniq.size - refute_includes(hosts_used, "127.0.0.3") - refute_includes(hosts_used, "127.0.0.4") + assert_includes(['127.0.0.3', '127.0.0.4'], hosts_used.uniq.first) cluster.close end - def test_raise_error_on_dc_aware_round_robin_unable_to_route_local_consistencies_to_remote + # Test for dc-aware round robin lbp with local down but remotes disabled for local consistencies + # + # test_dc_aware_round_robin_does_not_query_to_remote_dc_for_local_consistency_by_default_if_local_down tests the + # dc-aware round robin policy's behavior for local consistency queries when the local dc is down, but only remote + # hosts are enabled. It first specifies the load balancing policy with max_remote_hosts_to_use = 2 to allow + # up to 2 remote hosts to be used for non-local consistency queries. It then performs a query and verifies that a + # NoHostsAvailable error is raised, as no local nodes are available for the local consistency query. Note the default + # consistency is :local_one. + # + # @expected_errors [Cassandra::Errors::NoHostsAvailable] When local dc is down, local consistency is not enabled for remotes. + # + # @since 3.0.0 + # @jira_ticket RUBY-211 + # @expected_result A NoHostsAvailable should be raised as the local dc is down, and no remotes are specified for local consistency. + # + # @test_assumptions Existing Cassandra cluster with two datacenters, keyspace 'simplex', and table 'users'. + # @test_category load_balancing:round_robin + # + def test_dc_aware_round_robin_does_not_query_to_remote_dc_for_local_consistency_by_default_if_local_down setup_schema - datacenter = "dc2" - policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new(datacenter) - cluster = Cassandra.cluster(consistency: :one, load_balancing_policy: policy) - session = cluster.connect("simplex") + datacenter = 'dc1' + max_remote_hosts_to_use = 2 + policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new(datacenter, max_remote_hosts_to_use) + cluster = Cassandra.cluster(load_balancing_policy: policy) + session = cluster.connect('simplex') - @@ccm_cluster.stop_node('node3') - @@ccm_cluster.stop_node('node4') + @@ccm_cluster.stop_node('node1') + @@ccm_cluster.stop_node('node2') - assert_raises(Cassandra::Errors::NoHostsAvailable) do - session.execute("INSERT INTO users (user_id, first, last, age) VALUES (0, 'John', 'Doe', 40)", :consistency => :local_one) + assert_raises(Cassandra::Errors::NoHostsAvailable) do + session.execute("INSERT INTO users (user_id, first, last, age) VALUES (0, 'John', 'Doe', 40)").execution_info end cluster.close end - def test_dc_aware_round_robin_can_route_local_consistencies_to_remote + # Test for dc-aware round robin lbp with local down but remotes enabled for local consistencies + # + # test_dc_aware_round_robin_can_query_to_remote_dc_for_local_if_local_down tests the dc-aware round robin policy's + # behavior when the local dc is down, but remote hosts are enabled with local consistencies. It first specifies the + # load balancing policy with max_remote_hosts_to_use = 2, and use_remote_hosts_for_local_consistency = true, to allow + # up to 2 remote hosts to be used for local consistency queries. It then performs 4 queries and verifies that hosts + # from dc2 (node3, node4) are used. Note the default consistency is :local_one. + # + # @since 1.0.0 + # @expected_result Each of the 4 local-consistency queries should be routed a different node in the remote dc, dc2. + # + # @test_assumptions Existing Cassandra cluster with two datacenters, keyspace 'simplex', and table 'users'. + # @test_category load_balancing:round_robin + # + def test_dc_aware_round_robin_can_query_to_remote_dc_for_local_consistency_if_local_down setup_schema - datacenter = "dc2" - use_remote = true - policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new(datacenter, nil, use_remote) - cluster = Cassandra.cluster(consistency: :one, load_balancing_policy: policy) - session = cluster.connect("simplex") + datacenter = 'dc1' + max_remote_hosts_to_use = 2 + use_remote_hosts_for_local_consistency = true + policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new(datacenter, max_remote_hosts_to_use, + use_remote_hosts_for_local_consistency) + cluster = Cassandra.cluster(load_balancing_policy: policy) + session = cluster.connect('simplex') - @@ccm_cluster.stop_node('node3') - @@ccm_cluster.stop_node('node4') + @@ccm_cluster.stop_node('node1') + @@ccm_cluster.stop_node('node2') hosts_used = [] 4.times do - info = session.execute("INSERT INTO users (user_id, first, last, age) VALUES (0, 'John', 'Doe', 40)", - :consistency => :local_one).execution_info + info = session.execute("INSERT INTO users (user_id, first, last, age) VALUES (0, 'John', 'Doe', 40)").execution_info hosts_used.push(info.hosts.last.ip.to_s) end - assert_equal ['127.0.0.1', '127.0.0.2'], hosts_used.sort.uniq + assert_equal ['127.0.0.3', '127.0.0.4'], hosts_used.sort.uniq cluster.close end + # Test for dc-aware round robin lbp with local down but zero remotes enabled for local consistencies + # + # test_dc_aware_round_robin_cannot_query_to_remote_dc_for_local_consistency_if_max_hosts_zero tests the dc-aware + # round robin policy's behavior when the local dc is down, but zero remote hosts are enabled with local consistencies. + # It attempts to create a load balancing policy with max_remote_hosts_to_use = 0, + # and use_remote_hosts_for_local_consistency = true, and verifies than an ArgumentError is raised. + # + # @expected_errors [ArgumentError] When zero max_remote_hosts_to_use is specified with use_remote_hosts_for_local_consistency. + # + # @since 3.0.0 + # @jira_ticket RUBY-211 + # @expected_result An ArgumentError should be raised. + # + # @test_assumptions Existing Cassandra cluster with two datacenters, keyspace 'simplex', and table 'users'. + # @test_category load_balancing:round_robin + # + def test_dc_aware_round_robin_cannot_query_to_remote_dc_for_local_consistency_if_max_hosts_zero + setup_schema + datacenter = 'dc1' + max_remote_hosts_to_use = 0 + use_remote_hosts_for_local_consistency = true + + assert_raises(ArgumentError) do + Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new(datacenter, max_remote_hosts_to_use, + use_remote_hosts_for_local_consistency) + end + end + # Test for TryNextHost retry decision # # test_try_next_host_retry_decision tests that TryNextHost retry decision is being properly used when returned from the @@ -228,6 +333,7 @@ def test_dc_aware_round_robin_can_route_local_consistencies_to_remote # @expected_errors [Cassandra::Errors::NoHostsAvailable] When all hosts are down. # # @since 3.0.0 + # @jira_ticket RUBY-104 # @expected_result Each of the queries should be fulfilled by an available host. # # @test_assumptions Existing Cassandra cluster with keyspace 'simplex' and table 'users'. diff --git a/integration/load_balancing/token_aware_test.rb b/integration/load_balancing/token_aware_test.rb index 64d9efc86..c04c7f691 100644 --- a/integration/load_balancing/token_aware_test.rb +++ b/integration/load_balancing/token_aware_test.rb @@ -51,7 +51,7 @@ def test_token_aware_datacenter_aware_is_used_by_default cluster.close end - def test_token_aware_routes_to_primary_replica + def test_token_aware_routes_to_primary_replica_in_primary_dc setup_schema base_policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new('dc1') policy = Cassandra::LoadBalancing::Policies::TokenAware.new(base_policy) @@ -85,11 +85,46 @@ def test_token_aware_routes_to_primary_replica cluster.close end - def test_token_aware_routes_to_next_replica_if_primary_down + # Test for token-aware load balancing policy with primary dc down + # + # test_token_aware_does_not_by_default_route_to_next_replica_if_primary_dc_down tests the token aware policy's + # behavior when the primary dc is down. It performs a query and verifies that a NoHostsAvailable error is + # raised as no primary replicas are available. Note the default consistency is :local_one. + # + # @expected_errors [Cassandra::Errors::NoHostsAvailable] When primary dc is down. + # + # @since 1.0.0 + # @expected_result A NoHostsAvailable should be raised as the primary dc is down, and no remotes are specified. + # + # @test_assumptions Existing Cassandra cluster with two datacenters, keyspace 'simplex', and table 'users'. + # @test_category load_balancing:token_aware + # + def test_token_aware_does_not_by_default_route_to_next_replica_if_primary_dc_down setup_schema - base_policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new('dc1', nil) + datacenter = 'dc1' + base_policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new(datacenter) policy = Cassandra::LoadBalancing::Policies::TokenAware.new(base_policy) - cluster = Cassandra.cluster(consistency: :one, load_balancing_policy: policy) + cluster = Cassandra.cluster(load_balancing_policy: policy) + session = cluster.connect('simplex') + + select = Retry.with_attempts(5) { session.prepare("SELECT token(user_id) FROM simplex.users WHERE user_id = ?") } + + @@ccm_cluster.stop_node("node1") + @@ccm_cluster.stop_node("node2") + + assert_raises(Cassandra::Errors::NoHostsAvailable) do + Retry.with_attempts(5) { session.execute(select, arguments: [2]) } + end + + cluster.close + end + + def test_token_aware_can_route_to_next_replica_if_primary_dc_down + setup_schema + max_remote_hosts_to_use = nil + base_policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new('dc1', max_remote_hosts_to_use) + policy = Cassandra::LoadBalancing::Policies::TokenAware.new(base_policy) + cluster = Cassandra.cluster(load_balancing_policy: policy, consistency: :one) session = cluster.connect("simplex") Retry.with_attempts(5, Cassandra::Errors::InvalidError) do @@ -109,7 +144,7 @@ def test_token_aware_routes_to_next_replica_if_primary_down result = Retry.with_attempts(5) { session.execute(select, arguments: [2]) } assert_equal 1, result.execution_info.hosts.size - assert ["127.0.0.3", "127.0.0.4"].include?(result.execution_info.hosts.last.ip.to_s) + assert_includes(["127.0.0.3", "127.0.0.4"], result.execution_info.hosts.last.ip.to_s) end cluster.close @@ -121,7 +156,7 @@ def test_token_aware_routes_to_next_whitelisted_replica_if_primary_down round_robin = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new('dc1') whitelist = Cassandra::LoadBalancing::Policies::WhiteList.new(allowed_ips, round_robin) policy = Cassandra::LoadBalancing::Policies::TokenAware.new(whitelist) - cluster = Cassandra.cluster(consistency: :one, load_balancing_policy: policy) + cluster = Cassandra.cluster(load_balancing_policy: policy, consistency: :one) session = cluster.connect("simplex") Retry.with_attempts(5, Cassandra::Errors::InvalidError) do @@ -135,68 +170,78 @@ def test_token_aware_routes_to_next_whitelisted_replica_if_primary_down cluster.close end - def test_token_aware_routes_to_primary_replica_in_primary_dc + # Test for token-aware lbp with primary down but remotes disabled for local consistencies + # + # test_token_aware_does_not_route_to_next_replica_for_local_consistency_by_default_if_primary_dc_down tests the + # token-aware policy's behavior for local consistency queries when the primary dc is down, but only remote + # hosts are enabled. It first specifies the load balancing policy with max_remote_hosts_to_use = nil to allow + # unlimited remote hosts to be used for non-local consistency queries. It then performs a query and verifies that a + # NoHostsAvailable error is raised, as no primary replicas are available for the local consistency query. Note the + # default consistency is :local_one. + # + # @expected_errors [Cassandra::Errors::NoHostsAvailable] When primary dc is down, local consistency is not enabled for remotes. + # + # @since 3.0.0 + # @jira_ticket RUBY-211 + # @expected_result A NoHostsAvailable should be raised as the primary dc is down, and no remotes are specified for local consistency. + # + # @test_assumptions Existing Cassandra cluster with two datacenters, keyspace 'simplex', and table 'users'. + # @test_category load_balancing:token_aware + # + def test_token_aware_does_not_route_to_next_replica_for_local_consistency_by_default_if_primary_dc_down setup_schema - datacenter = "dc2" - base_policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new(datacenter) + datacenter = 'dc1' + max_remote_hosts_to_use = nil + base_policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new(datacenter, max_remote_hosts_to_use) policy = Cassandra::LoadBalancing::Policies::TokenAware.new(base_policy) cluster = Cassandra.cluster(load_balancing_policy: policy) - session = cluster.connect("simplex") - - Retry.with_attempts(5, Cassandra::Errors::InvalidError) do - select = Retry.with_attempts(5) { session.prepare("SELECT token(user_id) FROM simplex.users WHERE user_id = ?") } - - result = Retry.with_attempts(5) { session.execute(select, arguments: [0]) } - assert_equal 2945182322382062539, result.first.values.first - assert_equal "127.0.0.3", result.execution_info.hosts.last.ip.to_s + session = cluster.connect('simplex') - result = Retry.with_attempts(5) { session.execute(select, arguments: [1]) } - assert_equal 6292367497774912474, result.first.values.first - assert_equal "127.0.0.3", result.execution_info.hosts.last.ip.to_s + select = Retry.with_attempts(5) { session.prepare("SELECT token(user_id) FROM simplex.users WHERE user_id = ?") } - result = Retry.with_attempts(5) { session.execute(select, arguments: [2]) } - assert_equal -8218881827949364593, result.first.values.first - assert_equal "127.0.0.4", result.execution_info.hosts.last.ip.to_s + @@ccm_cluster.stop_node("node1") + @@ccm_cluster.stop_node("node2") - result = Retry.with_attempts(5) { session.execute(select, arguments: [3]) } - assert_equal -8048510690352527683, result.first.values.first - assert_equal "127.0.0.4", result.execution_info.hosts.last.ip.to_s + assert_raises(Cassandra::Errors::NoHostsAvailable) do + Retry.with_attempts(5) { session.execute(select, arguments: [2]) } end cluster.close end - def test_token_aware_routes_to_secondary_replica_if_primary_dc_down + # Test for token-aware lbp with primary down but remotes enabled for local consistencies + # + # test_token_aware_can_route_to_next_replica_for_local_consistency_if_primary_dc_down tests the token-aware policy's + # behavior when the primary dc is down, but remote replicas are enabled with local consistencies. It first specifies the + # load balancing policy with max_remote_hosts_to_use = nil, and use_remote_hosts_for_local_consistency = true, to allow + # unlimited remote hosts to be used for local consistency queries. It then performs 4 queries and verifies that replicas + # from dc2 (node3, node4) are used. Note the default consistency is :local_one. + # + # @since 1.0.0 + # @expected_result Each of the 4 local-consistency queries should be routed a different replica in the remote dc, dc2. + # + # @test_assumptions Existing Cassandra cluster with two datacenters, keyspace 'simplex', and table 'users'. + # @test_category load_balancing:token_aware + # + def test_token_aware_can_route_to_next_replica_for_local_consistency_if_primary_dc_down setup_schema - datacenter = "dc2" - base_policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new(datacenter, nil) + datacenter = 'dc1' + max_remote_hosts_to_use = nil + use_remote_hosts_for_local_consistency = true + base_policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new(datacenter, max_remote_hosts_to_use, + use_remote_hosts_for_local_consistency) policy = Cassandra::LoadBalancing::Policies::TokenAware.new(base_policy) - cluster = Cassandra.cluster(consistency: :one, load_balancing_policy: policy) - session = cluster.connect("simplex") - - Retry.with_attempts(5, Cassandra::Errors::InvalidError) do - select = Retry.with_attempts(5) { session.prepare("SELECT token(user_id) FROM simplex.users WHERE user_id = ?") } - - result = Retry.with_attempts(5) { session.execute(select, arguments: [2]) } - assert_equal 1, result.execution_info.hosts.size - assert_equal "127.0.0.4", result.execution_info.hosts.last.ip.to_s - - @@ccm_cluster.stop_node("node4") - - result = Retry.with_attempts(5) { session.execute(select, arguments: [2]) } - assert_equal 1, result.execution_info.hosts.size - assert_equal "127.0.0.3", result.execution_info.hosts.last.ip.to_s + cluster = Cassandra.cluster(load_balancing_policy: policy) + session = cluster.connect('simplex') - @@ccm_cluster.stop_node("node3") + select = Retry.with_attempts(5) { session.prepare("SELECT token(user_id) FROM simplex.users WHERE user_id = ?") } - result = Retry.with_attempts(5) { session.execute(select, arguments: [2], consistency: :one) } - assert_equal 1, result.execution_info.hosts.size - assert_equal "127.0.0.2", result.execution_info.hosts.last.ip.to_s + @@ccm_cluster.stop_node("node1") + @@ccm_cluster.stop_node("node2") - result = Retry.with_attempts(5) { session.execute(select, arguments: [2], consistency: :one) } - assert_equal 1, result.execution_info.hosts.size - assert_equal "127.0.0.1", result.execution_info.hosts.last.ip.to_s - end + result = Retry.with_attempts(5) { session.execute(select, arguments: [2]) } + assert_equal 1, result.execution_info.hosts.size + assert_includes(["127.0.0.3", "127.0.0.4"], result.execution_info.hosts.last.ip.to_s) cluster.close end From 7c0c1b8d673913c2f723eba0ba34be0cc5980963 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Tue, 17 May 2016 18:01:54 -0700 Subject: [PATCH 073/196] Minor doc tweaks for 3.0. --- README.md | 4 ++-- lib/cassandra.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fae0ff46c..214700a73 100644 --- a/README.md +++ b/README.md @@ -31,8 +31,8 @@ This driver is based on [the cql-rb gem](https://github.com/iconara/cql-rb) by [ This driver works exclusively with the Cassandra Query Language v3 (CQL3) and Cassandra's native protocol. The current version works with: -* Apache Cassandra versions 1.2, 2.0, 2.1, and 3.x -* DataStax Enterprise 4.0, 4.5, 4.6, 4.7, and 4.8 +* Apache Cassandra versions 1.2, 2.0, 2.1, 2.2, and 3.x +* DataStax Enterprise 4.0 and above. * Ruby (MRI) 2.2, 2.3 * JRuby 1.7 diff --git a/lib/cassandra.rb b/lib/cassandra.rb index e5db8591c..266431c6c 100644 --- a/lib/cassandra.rb +++ b/lib/cassandra.rb @@ -187,7 +187,7 @@ module Cassandra # v2 or earlier protocol. # # @option options [Integer] :protocol_version (nil) Version of protocol to speak to - # nodes. By default, this is auto-negotiated to the lowest common protocol version + # nodes. By default, this is auto-negotiated to the highest common protocol version # that all nodes in `:hosts` speak. # # @option options [Boolean, Cassandra::TimestampGenerator] :client_timestamps (false) whether the driver From 11fc8d637e2eff4af2e865dd94bd6ece731113a3 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Wed, 18 May 2016 10:22:13 -0700 Subject: [PATCH 074/196] More minor doc tweaks for 3.0. --- README.md | 44 +++++++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 214700a73..6c93e194d 100644 --- a/README.md +++ b/README.md @@ -96,28 +96,33 @@ Some of the new features added to the driver have unfortunately led to changes i ### Features: -* Apache Cassandra native protocol v4 -* Add support for smallint, tinyint, date (Cassandra::Date) and time (Cassandra::Time) data types. +* Add support for Apache Cassandra native protocol v4 +* Add support for smallint, tinyint, date (`Cassandra::Date`) and time (`Cassandra::Time`) data types. * Include schema metadata for User Defined Functions and User Defined Aggregates. +* 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. * Include client ip addresses in request traces, only on Cassandra 3.x. -* Add new retry policy decision Cassandra::Retry::Policy#try_next_host. -* Support specifying statement idempotence with the new :idempotent option when executing. -* Support sending custom payloads when preparing or executing statements using the new :payload option. -* Expose custom payloads received with responses on server exceptions and Cassandra::Execution::Info instances. -* Expose server warnings on server exceptions and Cassandra::Execution::Info instances. -* Add connections_per_local_node, connections_per_remote_node, requests_per_connection cluster configuration options to tune parallel query execution and resource usage. -* Add Cassandra::Logger class to make it easy for users to enable debug logging in the client. -* Add protocol_version configuration option to allow the user to force the protocol version to use for communication with nodes. +* Add new retry policy decision `Cassandra::Retry::Policy#try_next_host`. +* Support specifying statement idempotence with the new `idempotent` option when executing. +* Support sending custom payloads when preparing or executing statements using the new `payload` option. +* Expose custom payloads received with responses on server exceptions and `Cassandra::Execution::Info` instances. +* Expose server warnings on server exceptions and `Cassandra::Execution::Info` instances. +* Add `connections_per_local_node`, `connections_per_remote_node`, `requests_per_connection` cluster configuration options to tune parallel query execution and resource usage. +* Add `Cassandra::Logger` class to make it easy for users to enable debug logging in the client. +* Add `protocol_version` configuration option to allow the user to force the protocol version to use for communication with nodes. * Add support for materialized views and indexes in the schema metadata. +* Support the `ReadError`, `WriteError`, and `FunctionCallError` Cassandra error responses introduced in Cassandra 2.2. +* Add support for unset variables in bound statements. +* Support DSE security (`DseAuthenticator`, configured for LDAP). +* Add a timeout option to `Cassandra::Future#get`. -### Breaking Changes: +### Breaking Changes from 2.x: -* Cassandra::Future#join is now an alias to Cassandra::Future#get and will raise an error if the future is resolved with one. +* `Cassandra::Future#join` is now an alias to Cassandra::Future#get and will raise an error if the future is resolved with one. * Default consistency level is now LOCAL_ONE. * Enable tcp no-delay by default. * Unavailable errors are retried on the next host in the load balancing plan by default. -* Statement execution no longer retried on timeouts, unless :idempotent => true has been specified when executing. -* 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: +* 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. +* `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: ```ruby batch.add(query, ['val1', 'val2']) @@ -128,6 +133,10 @@ batch.add(query, {p1: 'val1'}) # becomes batch.add(query, arguments: {p1: 'val1'}) ``` +* The Datacenter-aware load balancing policy (`Cassandra::LoadBalancing::Policies::DCAwareRoundRobin`) defaults to using + nodes in the local DC only. In prior releases, the policy would fall back to remote nodes after exhausting local nodes. + Specify a positive value (or nil for unlimited) for `max_remote_hosts_to_use` when initializing the policy to allow remote node use. +* Unspecified variables in statements previously resulted in an exception. Now they are essentially ignored or treated as null. ### Bug Fixes: @@ -135,12 +144,13 @@ batch.add(query, arguments: {p1: 'val1'}) * [[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. * [[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. * [[RUBY-151](https://datastax-oss.atlassian.net/browse/RUBY-151)] Decode incomplete UDTs properly. -* [[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. +* [[RUBY-155](https://datastax-oss.atlassian.net/browse/RUBY-155)] Request timeout timer should not include request queuing time. +* [[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. +* [[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. ## Feedback Requested -*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). +*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). ## Code examples From df74ca293fbd263b2ab79735c163df25061f832f Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 23 May 2016 09:02:06 -0700 Subject: [PATCH 075/196] * Fixed non-existent variable reference in connector.rb. --- lib/cassandra/cluster/connector.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cassandra/cluster/connector.rb b/lib/cassandra/cluster/connector.rb index f37118dbd..2b7904212 100644 --- a/lib/cassandra/cluster/connector.rb +++ b/lib/cassandra/cluster/connector.rb @@ -283,7 +283,7 @@ def challenge_response_cycle(connection, authenticator, token) case r when Protocol::AuthChallengeResponse token = authenticator.challenge_response(r.token) - challenge_response_cycle(pending_connection, authenticator, token) + challenge_response_cycle(connection, authenticator, token) when Protocol::AuthSuccessResponse begin authenticator.authentication_successful(r.token) From 0ef47aaac0253c2cdd23ab487b3a66789416f1cb Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 23 May 2016 09:26:21 -0700 Subject: [PATCH 076/196] Prep for 3.0 GA release --- CHANGELOG.md | 2 +- Gemfile.lock | 2 +- README.md | 2 +- lib/cassandra/version.rb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cdb6ceab8..5406e4eda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# master +# 3.0.0 GA Features: * Increased default request timeout (the `timeout` option to `Cassandra.cluster`), from 10 seconds to 12 seconds because C* defaults to a 10 second timeout internally. The extra two seconds is buffer so that the client can diff --git a/Gemfile.lock b/Gemfile.lock index d843c739c..8eabace8f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - cassandra-driver (3.0.0.rc.2) + cassandra-driver (3.0.0) ione (~> 1.2) GEM diff --git a/README.md b/README.md index 6c93e194d..254ece9ef 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Datastax Ruby Driver for Apache Cassandra -*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).* +*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).* [![Build Status](https://travis-ci.org/datastax/ruby-driver.svg?branch=master)](https://travis-ci.org/datastax/ruby-driver) diff --git a/lib/cassandra/version.rb b/lib/cassandra/version.rb index 25cbd4fa8..13dee31db 100644 --- a/lib/cassandra/version.rb +++ b/lib/cassandra/version.rb @@ -17,5 +17,5 @@ #++ module Cassandra - VERSION = '3.0.0.rc.2'.freeze + VERSION = '3.0.0'.freeze end From 70cf649598f164400ca5ad134f3d8aa41db34a6a Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 23 May 2016 09:30:00 -0700 Subject: [PATCH 077/196] Updated docs.yaml --- docs.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs.yaml b/docs.yaml index 20139ff87..e8a747ecb 100644 --- a/docs.yaml +++ b/docs.yaml @@ -37,8 +37,8 @@ links: - title: Releases href: https://github.com/datastax/ruby-driver/releases versions: - - name: v3.0.0.rc.2 - ref: 24b4341b90fe412e2e10504e0adf9d0300fccecb + - name: v3.0.0 + ref: 0ef47aaac0253c2cdd23ab487b3a66789416f1cb - name: v2.1.6 ref: 474568501e5ef771a496c3a901f3cbdc3854f512 - name: v2.0.1 From cc29ca806925ce1c867c14eea31199043d35938f Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 23 May 2016 11:43:28 -0700 Subject: [PATCH 078/196] Added new master section to CHANGELOG. --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5406e4eda..eb6c2bbec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# master +Features: + +Bug Fixes: + + # 3.0.0 GA Features: * Increased default request timeout (the `timeout` option to `Cassandra.cluster`), from 10 seconds to 12 seconds @@ -14,7 +20,6 @@ Breaking Changes: nodes in the local DC only. In prior releases, the policy would fall back to remote nodes after exhausting local nodes. Specify a positive value (or nil for unlimited) for `max_remote_hosts_to_use` when initializing the policy to allow remote node use. - # 3.0.0 rc2 Features: * Add protocol_version configuration option to allow the user to force the protocol version to use for communication with nodes. From 7352be35c4b09ac2a444c30dcb091f1c80ab91b7 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Wed, 25 May 2016 18:34:19 -0700 Subject: [PATCH 079/196] * Plumb through node host to authentication provider, as some authentication providers need that info. --- lib/cassandra/auth.rb | 4 +++- lib/cassandra/cluster/connector.rb | 11 ++++------- lib/cassandra/cluster/options.rb | 13 +++++++++++-- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/lib/cassandra/auth.rb b/lib/cassandra/auth.rb index 803640e14..e029beaca 100644 --- a/lib/cassandra/auth.rb +++ b/lib/cassandra/auth.rb @@ -34,7 +34,7 @@ module Auth # # @see Cassandra::Auth::Providers class Provider - # @!method create_authenticator(authentication_class, protocol_version) + # @!method create_authenticator(authentication_class, host) # # Create a new authenticator object. This method will be called once per # connection that requires authentication. The auth provider can create @@ -45,6 +45,8 @@ class Provider # # @param authentication_class [String] the authentication class used by # the server. + # @param host [Cassandra::Host] the node to whom we're authenticating. + # # @return [Cassandra::Auth::Authenticator, nil] an object with an # interface matching {Cassandra::Auth::Authenticator} or `nil` if the # authentication class is not supported. diff --git a/lib/cassandra/cluster/connector.rb b/lib/cassandra/cluster/connector.rb index 2b7904212..829727afb 100644 --- a/lib/cassandra/cluster/connector.rb +++ b/lib/cassandra/cluster/connector.rb @@ -154,7 +154,7 @@ def do_connect(host) supported_cql_versions.first : '3.1.0' - startup_connection(connection, cql_version, compression) + startup_connection(host, connection, cql_version, compression) end f.fallback do |error| case error @@ -200,7 +200,7 @@ def do_connect(host) end end - def startup_connection(connection, cql_version, compression) + def startup_connection(host, connection, cql_version, compression) connection.send_request(Protocol::StartupRequest.new(cql_version, compression), @execution_options.timeout).flat_map do |r| case r @@ -213,12 +213,9 @@ def startup_connection(connection, cql_version, compression) Ione::Future.failed(cannot_authenticate_error) end else - authenticator = @connection_options.create_authenticator( - r.authentication_class) + authenticator = @connection_options.create_authenticator(r.authentication_class, host) if authenticator - challenge_response_cycle(connection, - authenticator, - authenticator.initial_response) + challenge_response_cycle(connection, authenticator, authenticator.initial_response) else Ione::Future.failed(cannot_authenticate_error) end diff --git a/lib/cassandra/cluster/options.rb b/lib/cassandra/cluster/options.rb index 739fd62d3..bebb4ef2b 100644 --- a/lib/cassandra/cluster/options.rb +++ b/lib/cassandra/cluster/options.rb @@ -80,8 +80,17 @@ def compression @compressor && @compressor.algorithm end - def create_authenticator(authentication_class) - @auth_provider && @auth_provider.create_authenticator(authentication_class) + def create_authenticator(authentication_class, host) + if @auth_provider + # Auth providers should take an auth-class and host, but they used to not, so for backward compatibility + # we figure out if this provider does, and if so send both args, otherwise just send the auth-class. + + if @auth_provider.method(:create_authenticator).arity == 1 + @auth_provider.create_authenticator(authentication_class) + else + @auth_provider.create_authenticator(authentication_class, host) + end + end end def connections_per_local_node From a520f40c2fc5c1d66ad70ecb55eb37eccadc6a38 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Sun, 29 May 2016 15:27:19 -0700 Subject: [PATCH 080/196] * Revert version number to 3.0.1. --- Gemfile.lock | 2 +- lib/cassandra/version.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index adf19f350..e3531d49c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - cassandra-driver (3.0.1.beta.1) + cassandra-driver (3.0.1) ione (~> 1.2) GEM diff --git a/lib/cassandra/version.rb b/lib/cassandra/version.rb index 0c5c5f1e4..3a04c0bbe 100644 --- a/lib/cassandra/version.rb +++ b/lib/cassandra/version.rb @@ -17,5 +17,5 @@ #++ module Cassandra - VERSION = '3.0.1.beta.1'.freeze + VERSION = '3.0.1'.freeze end From a84c7241230aef2236840a09a6b2b86674e553e9 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Thu, 2 Jun 2016 13:43:51 -0700 Subject: [PATCH 081/196] RUBY-220 - Plumbing to support custom types: serializing and deserializing custom domain objects defined by the client. --- lib/cassandra.rb | 2 + lib/cassandra/cluster/connector.rb | 3 +- lib/cassandra/cluster/options.rb | 9 +++- lib/cassandra/custom_data.rb | 51 +++++++++++++++++++ lib/cassandra/driver.rb | 4 +- lib/cassandra/protocol.rb | 7 +++ lib/cassandra/protocol/coder.rb | 36 ++++++++++--- lib/cassandra/protocol/cql_byte_buffer.rb | 21 ++++++++ .../protocol/cql_protocol_handler.rb | 5 +- .../responses/raw_rows_result_response.rb | 6 ++- lib/cassandra/protocol/v1.rb | 3 +- lib/cassandra/protocol/v3.rb | 3 +- lib/cassandra/protocol/v4.rb | 8 +-- lib/cassandra/util.rb | 1 + spec/cassandra/cluster/options_spec.rb | 2 +- 15 files changed, 139 insertions(+), 22 deletions(-) create mode 100644 lib/cassandra/custom_data.rb diff --git a/lib/cassandra.rb b/lib/cassandra.rb index ec12496b6..3689f8465 100644 --- a/lib/cassandra.rb +++ b/lib/cassandra.rb @@ -66,6 +66,7 @@ module Cassandra :connections_per_remote_node, :consistency, :credentials, + :custom_types, :datacenter, :futures_factory, :heartbeat_interval, @@ -801,6 +802,7 @@ def self.validate_and_massage_options(options) require 'cassandra/executors' require 'cassandra/future' require 'cassandra/cluster' +require 'cassandra/custom_data' require 'cassandra/driver' require 'cassandra/host' require 'cassandra/session' diff --git a/lib/cassandra/cluster/connector.rb b/lib/cassandra/cluster/connector.rb index 829727afb..f8769e71a 100644 --- a/lib/cassandra/cluster/connector.rb +++ b/lib/cassandra/cluster/connector.rb @@ -134,7 +134,8 @@ def do_connect(host) @connection_options.compressor, @connection_options.heartbeat_interval, @connection_options.idle_timeout, - @connection_options.requests_per_connection) + @connection_options.requests_per_connection, + @connection_options.custom_type_handlers) end.flat_map do |connection| # connection is a CqlProtocolHandler f = request_options(connection) diff --git a/lib/cassandra/cluster/options.rb b/lib/cassandra/cluster/options.rb index de1c229e4..892ddb753 100644 --- a/lib/cassandra/cluster/options.rb +++ b/lib/cassandra/cluster/options.rb @@ -24,7 +24,7 @@ class Options attr_reader :auth_provider, :compressor, :connect_timeout, :credentials, :heartbeat_interval, :idle_timeout, :port, :schema_refresh_delay, - :schema_refresh_timeout, :ssl + :schema_refresh_timeout, :ssl, :custom_type_handlers attr_boolean :protocol_negotiable, :synchronize_schema, :nodelay attr_accessor :protocol_version @@ -45,7 +45,8 @@ def initialize(logger, schema_refresh_delay, schema_refresh_timeout, nodelay, - requests_per_connection) + requests_per_connection, + custom_types) @logger = logger @protocol_version = protocol_version @credentials = credentials @@ -60,6 +61,10 @@ def initialize(logger, @schema_refresh_delay = schema_refresh_delay @schema_refresh_timeout = schema_refresh_timeout @nodelay = nodelay + @custom_type_handlers = {} + custom_types.each do |type_klass| + @custom_type_handlers[type_klass.type] = type_klass + end @connections_per_local_node = connections_per_local_node @connections_per_remote_node = connections_per_remote_node diff --git a/lib/cassandra/custom_data.rb b/lib/cassandra/custom_data.rb new file mode 100644 index 000000000..dad1266a6 --- /dev/null +++ b/lib/cassandra/custom_data.rb @@ -0,0 +1,51 @@ +# encoding: utf-8 + +#-- +# Copyright 2013-2016 DataStax, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + +# Use this module to mark domain object classes as custom type implementations for custom-type +# columns in C*. This module has no logic of its own, but indicates that the marked class has +# certain methods. +# @private +module Cassandra::CustomData + def self.included base + base.send :include, InstanceMethods + base.extend ClassMethods + end + + module ClassMethods + # @return [Cassandra::Types::Custom] the custom type that this class represents. + def type + raise NotImplementedError, "#{self.class} must implement the :type class method" + end + + # Deserialize the given data into an instance of this domain object class. + # @param data [String] byte-array representation of a column value of this custom type. + # @return An instance of the domain object class. + # @raise [Cassandra::Errors::DecodingError] upon failure. + def deserialize(data) + raise NotImplementedError, "#{self.class} must implement the :deserialize class method" + end + end + + module InstanceMethods + # Serialize this domain object into a byte array to send to C*. + # @return [String] byte-array representation of this domain object. + def serialize + raise NotImplementedError, "#{self.class} must implement the :serialize instance method" + end + end +end diff --git a/lib/cassandra/driver.rb b/lib/cassandra/driver.rb index c5bf258a4..509f27498 100644 --- a/lib/cassandra/driver.rb +++ b/lib/cassandra/driver.rb @@ -139,10 +139,12 @@ def self.let(name, &block) schema_refresh_delay, schema_refresh_timeout, nodelay, - requests_per_connection + requests_per_connection, + custom_types ) end + let(:custom_types) { [] } let(:port) { 9042 } let(:protocol_version) { nil } let(:connect_timeout) { 10 } diff --git a/lib/cassandra/protocol.rb b/lib/cassandra/protocol.rb index 819d7b3a2..05aa3c66b 100644 --- a/lib/cassandra/protocol.rb +++ b/lib/cassandra/protocol.rb @@ -28,6 +28,13 @@ module Formats BYTES_FORMAT = 'C*'.freeze TWO_INTS_FORMAT = 'NN'.freeze + + # All of the formats above are big-endian (e.g. network-byte-order). Some payloads (custom types) may have + # little-endian components. + + DOUBLE_FORMAT_LE = 'E'.freeze + INT_FORMAT_LE = 'V'.freeze + SHORT_FORMAT_LE = 'v'.freeze end module Constants diff --git a/lib/cassandra/protocol/coder.rb b/lib/cassandra/protocol/coder.rb index 4538f50e4..17c0e78e7 100644 --- a/lib/cassandra/protocol/coder.rb +++ b/lib/cassandra/protocol/coder.rb @@ -97,6 +97,7 @@ def write_value_v4(buffer, value, type) when :bigint, :counter then write_bigint(buffer, value) when :blob then write_blob(buffer, value) when :boolean then write_boolean(buffer, value) + when :custom then write_custom(buffer, value, type) when :decimal then write_decimal(buffer, value) when :double then write_double(buffer, value) when :float then write_float(buffer, value) @@ -221,19 +222,19 @@ def read_type_v4(buffer) end end - def read_values_v4(buffer, column_metadata) + def read_values_v4(buffer, column_metadata, custom_type_handlers) ::Array.new(buffer.read_int) do |_i| row = ::Hash.new column_metadata.each do |(_, _, column, type)| - row[column] = read_value_v4(buffer, type) + row[column] = read_value_v4(buffer, type, custom_type_handlers) end row end end - def read_value_v4(buffer, type) + def read_value_v4(buffer, type, custom_type_handlers) case type.kind when :ascii then read_ascii(buffer) when :bigint, :counter then read_bigint(buffer) @@ -253,11 +254,12 @@ def read_value_v4(buffer, type) when :smallint then read_smallint(buffer) when :time then read_time(buffer) when :date then read_date(buffer) + when :custom then read_custom(buffer, type, custom_type_handlers) when :list return nil unless read_size(buffer) value_type = type.value_type - ::Array.new(buffer.read_signed_int) { read_value_v4(buffer, value_type) } + ::Array.new(buffer.read_signed_int) { read_value_v4(buffer, value_type, custom_type_handlers) } when :map return nil unless read_size(buffer) @@ -266,7 +268,7 @@ def read_value_v4(buffer, type) value = ::Hash.new buffer.read_signed_int.times do - value[read_value_v4(buffer, key_type)] = read_value_v4(buffer, value_type) + value[read_value_v4(buffer, key_type, custom_type_handlers)] = read_value_v4(buffer, value_type, custom_type_handlers) end value @@ -277,7 +279,7 @@ def read_value_v4(buffer, type) value = ::Set.new buffer.read_signed_int.times do - value << read_value_v4(buffer, value_type) + value << read_value_v4(buffer, value_type, custom_type_handlers) end value @@ -295,7 +297,7 @@ def read_value_v4(buffer, type) values[field.name] = if length - buffer.length >= size nil else - read_value_v4(buffer, field.type) + read_value_v4(buffer, field.type, custom_type_handlers) end end @@ -308,7 +310,7 @@ def read_value_v4(buffer, type) members.each do |member_type| break if buffer.empty? - values << read_value_v4(buffer, member_type) + values << read_value_v4(buffer, member_type, custom_type_handlers) end values.fill(nil, values.length, (members.length - values.length)) @@ -774,6 +776,15 @@ def read_boolean(buffer) read_size(buffer) && buffer.read(1) == Constants::TRUE_BYTE end + def read_custom(buffer, type, custom_type_handlers) + # Lookup the type-name to get the Class that can deserialize buffer data into a custom domain object. + unless custom_type_handlers && custom_type_handlers.key?(type) + raise Errors::DecodingError, %(Unsupported custom column type: #{type.name}) + end + num_bytes = read_size(buffer) + custom_type_handlers[type].deserialize(buffer.read(num_bytes)) if num_bytes > 0 + end + def read_decimal(buffer) size = read_size(buffer) size && buffer.read_decimal(size) @@ -860,6 +871,15 @@ def write_boolean(buffer, value) buffer.append(value ? Constants::TRUE_BYTE : Constants::FALSE_BYTE) end + def write_custom(buffer, value, type) + # Verify that the given type-name matches the value's cql type name. + if value.class.type != type + raise Errors::EncodingError, "type mismatch: value is a #{value.type} and column is a #{type}" + end + + buffer.append_bytes(value.serialize) + end + def write_decimal(buffer, value) buffer.append_bytes(CqlByteBuffer.new.append_decimal(value)) end diff --git a/lib/cassandra/protocol/cql_byte_buffer.rb b/lib/cassandra/protocol/cql_byte_buffer.rb index 0e098d17d..b936f1b99 100644 --- a/lib/cassandra/protocol/cql_byte_buffer.rb +++ b/lib/cassandra/protocol/cql_byte_buffer.rb @@ -92,6 +92,13 @@ def read_double "Not enough bytes available to decode a double: #{e.message}", e.backtrace end + def read_double_le + read(8).unpack(Formats::DOUBLE_FORMAT_LE).first + rescue RangeError => e + raise Errors::DecodingError, + "Not enough bytes available to decode a double: #{e.message}", e.backtrace + end + def read_float read(4).unpack(Formats::FLOAT_FORMAT).first rescue RangeError => e @@ -108,6 +115,13 @@ def read_signed_int "Not enough bytes available to decode an int: #{e.message}", e.backtrace end + def read_unsigned_int_le + read(4).unpack(Formats::INT_FORMAT_LE).first + rescue RangeError => e + raise Errors::DecodingError, + "Not enough bytes available to decode an int: #{e.message}", e.backtrace + end + def read_unsigned_short read_short rescue RangeError => e @@ -115,6 +129,13 @@ def read_unsigned_short "Not enough bytes available to decode a short: #{e.message}", e.backtrace end + def read_unsigned_short_le + read(2).unpack(Formats::SHORT_FORMAT_LE).first + rescue RangeError => e + raise Errors::DecodingError, + "Not enough bytes available to decode a short: #{e.message}", e.backtrace + end + def read_string length = read_unsigned_short string = read(length) diff --git a/lib/cassandra/protocol/cql_protocol_handler.rb b/lib/cassandra/protocol/cql_protocol_handler.rb index aed900533..705684f6a 100644 --- a/lib/cassandra/protocol/cql_protocol_handler.rb +++ b/lib/cassandra/protocol/cql_protocol_handler.rb @@ -46,7 +46,8 @@ def initialize(connection, compressor = nil, heartbeat_interval = 30, idle_timeout = 60, - requests_per_connection = 128) + requests_per_connection = 128, + custom_type_handlers = {}) @protocol_version = protocol_version @connection = connection @scheduler = scheduler @@ -60,7 +61,7 @@ def initialize(connection, if protocol_version > 3 @frame_encoder = V4::Encoder.new(@compressor, protocol_version) - @frame_decoder = V4::Decoder.new(self, @compressor) + @frame_decoder = V4::Decoder.new(self, @compressor, custom_type_handlers) elsif protocol_version > 2 @frame_encoder = V3::Encoder.new(@compressor, protocol_version) @frame_decoder = V3::Decoder.new(self, @compressor) diff --git a/lib/cassandra/protocol/responses/raw_rows_result_response.rb b/lib/cassandra/protocol/responses/raw_rows_result_response.rb index 50268279b..50e88ee8a 100644 --- a/lib/cassandra/protocol/responses/raw_rows_result_response.rb +++ b/lib/cassandra/protocol/responses/raw_rows_result_response.rb @@ -24,17 +24,19 @@ def initialize(custom_payload, protocol_version, raw_rows, paging_state, - trace_id) + trace_id, + custom_type_handlers = nil) super(custom_payload, warnings, nil, nil, paging_state, trace_id) @protocol_version = protocol_version @raw_rows = raw_rows + @custom_type_handlers = custom_type_handlers end def materialize(metadata) @metadata = metadata @rows = if @protocol_version == 4 - Coder.read_values_v4(@raw_rows, @metadata) + Coder.read_values_v4(@raw_rows, @metadata, @custom_type_handlers) elsif @protocol_version == 3 Coder.read_values_v3(@raw_rows, @metadata) else diff --git a/lib/cassandra/protocol/v1.rb b/lib/cassandra/protocol/v1.rb index 1b79e95d3..092d3ec61 100644 --- a/lib/cassandra/protocol/v1.rb +++ b/lib/cassandra/protocol/v1.rb @@ -241,7 +241,8 @@ def decode_response(opcode, protocol_version, buffer, size, trace_id) protocol_version, remaining_bytes, paging_state, - trace_id) + trace_id, + nil) else RowsResultResponse.new(nil, nil, diff --git a/lib/cassandra/protocol/v3.rb b/lib/cassandra/protocol/v3.rb index ae6e67cf7..8409f06b7 100644 --- a/lib/cassandra/protocol/v3.rb +++ b/lib/cassandra/protocol/v3.rb @@ -275,7 +275,8 @@ def decode_response(opcode, protocol_version, buffer, size, trace_id) protocol_version, remaining_bytes, paging_state, - trace_id) + trace_id, + nil) else RowsResultResponse.new(nil, nil, diff --git a/lib/cassandra/protocol/v4.rb b/lib/cassandra/protocol/v4.rb index b4861258e..4ff3802cf 100644 --- a/lib/cassandra/protocol/v4.rb +++ b/lib/cassandra/protocol/v4.rb @@ -59,7 +59,7 @@ def write_parameters(buffer, params, types, names = EMPTY_LIST) end class Decoder - def initialize(handler, compressor = nil) + def initialize(handler, compressor = nil, custom_type_handlers = {}) @handler = handler @compressor = compressor @state = :initial @@ -68,6 +68,7 @@ def initialize(handler, compressor = nil) @code = nil @length = nil @buffer = CqlByteBuffer.new + @custom_type_handlers = custom_type_handlers end def <<(data) @@ -325,11 +326,12 @@ def decode_response(opcode, protocol_version, remaining_bytes, paging_state, - trace_id) + trace_id, + @custom_type_handlers) else RowsResultResponse.new(custom_payload, warnings, - Coder.read_values_v4(buffer, column_specs), + Coder.read_values_v4(buffer, column_specs, @custom_type_handlers), column_specs, paging_state, trace_id) diff --git a/lib/cassandra/util.rb b/lib/cassandra/util.rb index b07f99a2a..a944e20f1 100644 --- a/lib/cassandra/util.rb +++ b/lib/cassandra/util.rb @@ -196,6 +196,7 @@ def guess_type(object) Types.udt(object.keyspace, object.name, object.types) when UDT Types.udt('unknown', 'unknown', object.map {|k, v| [k, guess_type(v)]}) + when Cassandra::CustomData then object.class.type else raise ::ArgumentError, "Unable to guess the type of the argument: #{object.inspect}" diff --git a/spec/cassandra/cluster/options_spec.rb b/spec/cassandra/cluster/options_spec.rb index dbd91540b..6aff36295 100644 --- a/spec/cassandra/cluster/options_spec.rb +++ b/spec/cassandra/cluster/options_spec.rb @@ -26,7 +26,7 @@ def make_options(logger, Cassandra::Cluster::Options.new( logger, protocol_version, nil, nil, nil, nil, nil, false, connections_per_local_node, connections_per_remote_node, 60, 30, true, 1, 10, - true, requests_per_connection) + true, requests_per_connection, []) end module Cassandra From 000d54bc8739c201899a1e0ba11d2a54ac2495cd Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 6 Jun 2016 17:52:44 -0700 Subject: [PATCH 082/196] RUBY-220 - Plumbing to support custom types: serializing and deserializing custom domain objects defined by the client. * Fixed a reference to read_value_v4 that got missed earlier. --- lib/cassandra/cluster/schema/fetchers.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cassandra/cluster/schema/fetchers.rb b/lib/cassandra/cluster/schema/fetchers.rb index 2c80dbaef..8873eab8d 100644 --- a/lib/cassandra/cluster/schema/fetchers.rb +++ b/lib/cassandra/cluster/schema/fetchers.rb @@ -836,7 +836,7 @@ def create_aggregate(aggregate_data, functions) initial_state = Util.encode_object( Protocol::Coder.read_value_v4( Protocol::CqlByteBuffer.new.append_bytes(aggregate_data['initcond']), - state_type)) + state_type, nil)) # The state-function takes arguments: first the stype, then the args of the aggregate. state_function = functions.get(aggregate_data['state_func'], From 7089ad7e7b59381646df9aa54241576f3ac23c37 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Tue, 7 Jun 2016 14:27:34 -0700 Subject: [PATCH 083/196] RUBY-220 - Plumbing to support custom types: serializing and deserializing custom domain objects defined by the client. * Fixed bug where null values for custom type columns weren't handled properly. --- lib/cassandra/protocol/coder.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cassandra/protocol/coder.rb b/lib/cassandra/protocol/coder.rb index 17c0e78e7..e79dc18c5 100644 --- a/lib/cassandra/protocol/coder.rb +++ b/lib/cassandra/protocol/coder.rb @@ -782,7 +782,7 @@ def read_custom(buffer, type, custom_type_handlers) raise Errors::DecodingError, %(Unsupported custom column type: #{type.name}) end num_bytes = read_size(buffer) - custom_type_handlers[type].deserialize(buffer.read(num_bytes)) if num_bytes > 0 + custom_type_handlers[type].deserialize(buffer.read(num_bytes)) if num_bytes && num_bytes > 0 end def read_decimal(buffer) From be5696dd6c8b81eb2b724cc367ce46177ec27cf7 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Tue, 7 Jun 2016 16:07:49 -0700 Subject: [PATCH 084/196] RUBY-220 - Plumbing to support custom types: serializing and deserializing custom domain objects defined by the client. * Handle custom type validation in binding prepared statements properly. --- lib/cassandra/types.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/cassandra/types.rb b/lib/cassandra/types.rb index a6a368694..82fe0f819 100644 --- a/lib/cassandra/types.rb +++ b/lib/cassandra/types.rb @@ -1389,8 +1389,8 @@ def new(*value) # @raise [ArgumentError] if the value is invalid # @return [void] def assert(value, message = nil, &block) - raise ::NotImplementedError, - "unable to assert a value for custom type: #{@name.inspect}" + Util.assert_instance_of(CustomData, value, message, &block) + Util.assert_equal(self, value.class.type, message, &block) end # @return [String] a cassandra representation of this type From 468d63445b2fd005a67ca52c9f9c647085f3678a Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Tue, 7 Jun 2016 17:33:03 -0700 Subject: [PATCH 085/196] RUBY-220 - Plumbing to support custom types: serializing and deserializing custom domain objects defined by the client. * Handle custom types in cql_type_parser. --- lib/cassandra/cluster/schema/cql_type_parser.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/cassandra/cluster/schema/cql_type_parser.rb b/lib/cassandra/cluster/schema/cql_type_parser.rb index ed399f499..0313365b3 100644 --- a/lib/cassandra/cluster/schema/cql_type_parser.rb +++ b/lib/cassandra/cluster/schema/cql_type_parser.rb @@ -72,6 +72,9 @@ def lookup_type(node, types) Cassandra::Types.tuple(*node.children.map { |t| lookup_type(t, types)}) when 'empty' then Cassandra::Types.custom('org.apache.cassandra.db.marshal.EmptyType') + when /^'/ then + # Custom type. + Cassandra::Types.custom(node.name[1..-2]) else types.fetch(node.name) do raise IncompleteTypeError, "unable to lookup type #{node.name.inspect}" From 8f60fea7f9346d0c0396aaeddd9d583a6515d0ad Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Tue, 7 Jun 2016 18:47:04 -0700 Subject: [PATCH 086/196] RUBY-220 - Plumbing to support custom types: serializing and deserializing custom domain objects defined by the client. * Fix bug in prepared-statement processing to enable insertion into tables with a custom-type partition-key. --- lib/cassandra/statements/prepared.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/cassandra/statements/prepared.rb b/lib/cassandra/statements/prepared.rb index 822840943..09c763c85 100644 --- a/lib/cassandra/statements/prepared.rb +++ b/lib/cassandra/statements/prepared.rb @@ -185,7 +185,9 @@ def create_partition_key(values) 'the partition key and must be present.' end - if @connection_options.protocol_version >= 3 + if @connection_options.protocol_version >= 4 + Protocol::Coder.write_value_v4(buffer, value, type) + elsif @connection_options.protocol_version >= 3 Protocol::Coder.write_value_v3(buffer, value, type) else Protocol::Coder.write_value_v1(buffer, value, type) @@ -205,7 +207,9 @@ def create_partition_key(values) 'the partition key and must be present.' end - if @connection_options.protocol_version >= 3 + if @connection_options.protocol_version >= 4 + Protocol::Coder.write_value_v4(buffer, value, type) + elsif @connection_options.protocol_version >= 3 Protocol::Coder.write_value_v3(buf, value, type) else Protocol::Coder.write_value_v1(buf, value, type) From 00f9d4c289bb3405476a657afa4adcb17a4f1260 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Tue, 7 Jun 2016 18:56:29 -0700 Subject: [PATCH 087/196] RUBY-220 - Plumbing to support custom types: serializing and deserializing custom domain objects defined by the client. * Update Tuple.inspect to be consistent with how other object .inspect's are formatted. --- lib/cassandra/tuple.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/cassandra/tuple.rb b/lib/cassandra/tuple.rb index 984daff1e..585bf0fca 100644 --- a/lib/cassandra/tuple.rb +++ b/lib/cassandra/tuple.rb @@ -55,7 +55,7 @@ def size end def inspect - "#" + "#" end end @@ -114,7 +114,7 @@ def to_s # @private def inspect - "#" + "#" end # @private From 9d19fc86f086be5275ee38e054ebba89133f76b0 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Tue, 7 Jun 2016 20:00:25 -0700 Subject: [PATCH 088/196] RUBY-220 - Plumbing to support custom types: serializing and deserializing custom domain objects defined by the client. * Fix copy-paste error in prepared statement processing. --- lib/cassandra/statements/prepared.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cassandra/statements/prepared.rb b/lib/cassandra/statements/prepared.rb index 09c763c85..77d3e9210 100644 --- a/lib/cassandra/statements/prepared.rb +++ b/lib/cassandra/statements/prepared.rb @@ -208,7 +208,7 @@ def create_partition_key(values) end if @connection_options.protocol_version >= 4 - Protocol::Coder.write_value_v4(buffer, value, type) + Protocol::Coder.write_value_v4(buf, value, type) elsif @connection_options.protocol_version >= 3 Protocol::Coder.write_value_v3(buf, value, type) else From 5e9aa43d51c3e25dca0dbbc534baadf29ad16248 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Thu, 9 Jun 2016 13:54:25 -0700 Subject: [PATCH 089/196] RUBY-219 - SortedSet first-time initialization is unstable in a multi-threaded environment. * Fixed by having the C* module initialize a SortedSet upon module load (prior to multiple threads messing with SortedSet). --- CHANGELOG.md | 1 + lib/cassandra.rb | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb6c2bbec..51620473c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ Features: Bug Fixes: +* [RUBY-219](https://datastax-oss.atlassian.net/browse/RUBY-219) Sometimes get stack trace in metadata.rb due to failure in SortedSet initialization. # 3.0.0 GA diff --git a/lib/cassandra.rb b/lib/cassandra.rb index ec12496b6..dc7b63ee4 100644 --- a/lib/cassandra.rb +++ b/lib/cassandra.rb @@ -834,6 +834,11 @@ def self.validate_and_massage_options(options) # murmur3 hash extension require 'cassandra_murmur3' +# SortedSet has a race condition where it does some class/global initialization when the first instance is created. +# If this is done in a multi-threaded environment, bad things can happen. So force the initialization here, +# when loading the C* module. +::SortedSet.new + module Cassandra # @private VOID_STATEMENT = Statements::Void.new From 132b9831da40943381e2600a6744be5c826434a7 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 13 Jun 2016 11:36:07 -0700 Subject: [PATCH 090/196] RUBY-220 * Use \A instead of ^ for regex in case statement. --- lib/cassandra/cluster/schema/cql_type_parser.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cassandra/cluster/schema/cql_type_parser.rb b/lib/cassandra/cluster/schema/cql_type_parser.rb index 0313365b3..faf0cb37f 100644 --- a/lib/cassandra/cluster/schema/cql_type_parser.rb +++ b/lib/cassandra/cluster/schema/cql_type_parser.rb @@ -72,7 +72,7 @@ def lookup_type(node, types) Cassandra::Types.tuple(*node.children.map { |t| lookup_type(t, types)}) when 'empty' then Cassandra::Types.custom('org.apache.cassandra.db.marshal.EmptyType') - when /^'/ then + when /\A'/ then # Custom type. Cassandra::Types.custom(node.name[1..-2]) else From 82f756ccd54b03a05f42e920b5b3ae0f24b2861a Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Wed, 15 Jun 2016 14:25:29 -0700 Subject: [PATCH 091/196] RUBY-231 - support explicitly specified nil timeout option --- lib/cassandra/execution/options.rb | 2 +- spec/cassandra/execution/options_spec.rb | 41 ++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 spec/cassandra/execution/options_spec.rb diff --git a/lib/cassandra/execution/options.rb b/lib/cassandra/execution/options.rb index 333998787..36365f78d 100644 --- a/lib/cassandra/execution/options.rb +++ b/lib/cassandra/execution/options.rb @@ -177,7 +177,7 @@ def initialize(options, trusted_options = nil) @consistency = consistency || trusted_options.consistency @page_size = page_size || trusted_options.page_size @trace = trace.nil? ? trusted_options.trace? : !!trace - @timeout = timeout || trusted_options.timeout + @timeout = options.key?(:timeout) ? timeout : trusted_options.timeout @serial_consistency = serial_consistency || trusted_options.serial_consistency @arguments = arguments || trusted_options.arguments @type_hints = type_hints || trusted_options.type_hints diff --git a/spec/cassandra/execution/options_spec.rb b/spec/cassandra/execution/options_spec.rb new file mode 100644 index 000000000..a8f98c304 --- /dev/null +++ b/spec/cassandra/execution/options_spec.rb @@ -0,0 +1,41 @@ +# encoding: utf-8 + +#-- +# Copyright 2013-2016 DataStax, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + +require 'spec_helper' + +module Cassandra + module Execution + describe(Options) do + let(:base_options) { Options.new(timeout: 10, consistency: :one) } + it 'should allow nil timeout to override base non-nil timeout option' do + result = Options.new({timeout: nil}, base_options) + expect(result.timeout).to be_nil + end + + it 'should non-nil timeout to override base non-nil timeout option' do + result = Options.new({timeout: 123}, base_options) + expect(result.timeout).to eq(123) + end + + it 'should not override base timeout if not specified' do + result = Options.new({}, base_options) + expect(result.timeout).to eq(10) + end + end + end +end From 6c64f6da5e06c6e7a79729117f26a08fe8d50645 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Thu, 16 Jun 2016 13:55:43 -0700 Subject: [PATCH 092/196] RUBY-233 - Only retry failed queries that are idempotent when there is a client timeout. --- CHANGELOG.md | 3 +- lib/cassandra/cluster/client.rb | 70 +++++++++++++----------- spec/cassandra/cluster/client_spec.rb | 78 ++++++++++++++++++++++++--- 3 files changed, 112 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51620473c..dedf04279 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,8 @@ Features: Bug Fixes: * [RUBY-219](https://datastax-oss.atlassian.net/browse/RUBY-219) Sometimes get stack trace in metadata.rb due to failure in SortedSet initialization. - +* [RUBY-231](https://datastax-oss.atlassian.net/browse/RUBY-231) Driver ignores explicitly specified nil timeout (to indicate no time limit on query execution). +* [RUBY-233](https://datastax-oss.atlassian.net/browse/RUBY-233) Client timeout errors are retried for non-idempotent statements. # 3.0.0 GA Features: diff --git a/lib/cassandra/cluster/client.rb b/lib/cassandra/cluster/client.rb index 30f982d66..a0cc26645 100644 --- a/lib/cassandra/cluster/client.rb +++ b/lib/cassandra/cluster/client.rb @@ -1390,38 +1390,44 @@ def handle_response(response_future, end else response_future.on_failure do |ex| - errors[host] = ex - case request - when Protocol::QueryRequest, Protocol::PrepareRequest - send_request_by_plan(promise, - keyspace, - statement, - options, - request, - plan, - timeout, - errors, - hosts) - when Protocol::ExecuteRequest - execute_by_plan(promise, - keyspace, - statement, - options, - request, - plan, - timeout, - errors, - hosts) - when Protocol::BatchRequest - batch_by_plan(promise, - keyspace, - statement, - options, - request, - plan, - timeout, - errors, - hosts) + if ex.is_a?(Errors::HostError) || + (ex.is_a?(Errors::TimeoutError) && statement.idempotent?) + + errors[host] = ex + case request + when Protocol::QueryRequest, Protocol::PrepareRequest + send_request_by_plan(promise, + keyspace, + statement, + options, + request, + plan, + timeout, + errors, + hosts) + when Protocol::ExecuteRequest + execute_by_plan(promise, + keyspace, + statement, + options, + request, + plan, + timeout, + errors, + hosts) + when Protocol::BatchRequest + batch_by_plan(promise, + keyspace, + statement, + options, + request, + plan, + timeout, + errors, + hosts) + else + promise.break(ex) + end else promise.break(ex) end diff --git a/spec/cassandra/cluster/client_spec.rb b/spec/cassandra/cluster/client_spec.rb index ca797d237..4dec5d764 100644 --- a/spec/cassandra/cluster/client_spec.rb +++ b/spec/cassandra/cluster/client_spec.rb @@ -458,7 +458,7 @@ class Cluster attempts << connection.host if count == 0 count += 1 - raise Cassandra::Errors::ClientError.new + raise Cassandra::Errors::InternalError.new else Cassandra::Protocol::RowsResultResponse.new(nil, nil, [], [], nil, nil) end @@ -474,6 +474,72 @@ class Cluster expect(attempts).to eq(hosts) end + it 'does not retry on client timeout error if statement is not idempotent' do + attempts = [] + io_reactor.on_connection do |connection| + connection.handle_request do |request| + case request + when Cassandra::Protocol::OptionsRequest + Cassandra::Protocol::SupportedResponse.new({}) + when Cassandra::Protocol::StartupRequest + Cassandra::Protocol::ReadyResponse.new + when Cassandra::Protocol::QueryRequest + case request.cql + when 'SELECT * FROM songs' + attempts << connection.host + raise Cassandra::Errors::TimeoutError.new + else + Cassandra::Protocol::RowsResultResponse.new(nil, nil, [], [], nil, nil) + end + end + end + end + client.connect.value + future = client.query(Statements::Simple.new('SELECT * FROM songs'), + Execution::Options.new(:consistency => :one)) + got_excp = false + future.on_failure do |ex| + got_excp = true + expect(ex).to be_a(Cassandra::Errors::TimeoutError) + end + expect(got_excp) + expect(attempts).to have(1).items + expect(attempts.first).to eq(hosts.first) + end + + it 'retries on client timeout error if statement is idempotent' do + count = 0 + attempts = [] + io_reactor.on_connection do |connection| + connection.handle_request do |request| + case request + when Cassandra::Protocol::OptionsRequest + Cassandra::Protocol::SupportedResponse.new({}) + when Cassandra::Protocol::StartupRequest + Cassandra::Protocol::ReadyResponse.new + when Cassandra::Protocol::QueryRequest + case request.cql + when 'SELECT * FROM songs' + attempts << connection.host + if count == 0 + count += 1 + raise raise Cassandra::Errors::TimeoutError.new + else + Cassandra::Protocol::RowsResultResponse.new(nil, nil, [], [], nil, nil) + end + else + Cassandra::Protocol::RowsResultResponse.new(nil, nil, [], [], nil, nil) + end + end + end + end + client.connect.value + client.query(Statements::Simple.new('SELECT * FROM songs', nil, nil, true), + Execution::Options.new(:consistency => :one)).get + expect(attempts).to have(2).items + expect(attempts).to eq(hosts) + end + it 'raises if all hosts failed' do io_reactor.on_connection do |connection| connection.handle_request do |request| @@ -485,7 +551,7 @@ class Cluster when Cassandra::Protocol::QueryRequest case request.cql when 'SELECT * FROM songs' - raise Cassandra::Errors::ClientError.new + raise Cassandra::Errors::InternalError.new else Cassandra::Protocol::RowsResultResponse.new(nil, nil, [], [], nil, nil) end @@ -692,7 +758,7 @@ class Cluster attempts << connection.host if count == 0 count += 1 - raise Cassandra::Errors::ClientError.new + raise Cassandra::Errors::InternalError.new end Cassandra::Protocol::RowsResultResponse.new(nil, nil, [], [], nil, nil) end @@ -769,7 +835,7 @@ class Cluster when Cassandra::Protocol::PrepareRequest Protocol::PreparedResultResponse.new(nil, nil, 123, [], [], nil, nil) when Cassandra::Protocol::ExecuteRequest - raise Cassandra::Errors::ClientError.new + raise Cassandra::Errors::InternalError.new end end end @@ -918,7 +984,7 @@ class Cluster attempts << connection.host if count == 0 count += 1 - raise Cassandra::Errors::ClientError.new + raise Cassandra::Errors::InternalError.new end Cassandra::Protocol::RowsResultResponse.new(nil, nil, [], [], nil, nil) end @@ -943,7 +1009,7 @@ class Cluster when Cassandra::Protocol::StartupRequest Cassandra::Protocol::ReadyResponse.new when Cassandra::Protocol::BatchRequest - raise Cassandra::Errors::ClientError.new + raise Cassandra::Errors::InternalError.new end end end From 491a2848c032b49b36dedfc2c7373ade51b8f915 Mon Sep 17 00:00:00 2001 From: Kishan Karunaratne Date: Fri, 17 Jun 2016 10:51:59 -0700 Subject: [PATCH 093/196] [RUBY-233] Integration tests for idempotent statement timeouts --- build.yaml | 2 +- integration/idempotency_test.rb | 63 ++++++++++++++++++++++++---- integration/integration_test_case.rb | 2 +- 3 files changed, 56 insertions(+), 11 deletions(-) diff --git a/build.yaml b/build.yaml index 23d8a0654..64cd5a76a 100644 --- a/build.yaml +++ b/build.yaml @@ -7,7 +7,7 @@ cassandra: - 2.1 - 2.2 - 3.0 - - 3.4 + - 3.7 os: - ubuntu/trusty64 build: diff --git a/integration/idempotency_test.rb b/integration/idempotency_test.rb index 9689dacf6..7f1ab16c2 100644 --- a/integration/idempotency_test.rb +++ b/integration/idempotency_test.rb @@ -20,12 +20,12 @@ class IdempotencyTest < IntegrationTestCase def self.before_suite - @@ccm_cluster = CCM.setup_cluster(1, 2) + @@ccm_cluster = CCM.setup_cluster(2, 1) end def setup @@ccm_cluster.setup_schema(<<-CQL) - CREATE KEYSPACE simplex WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 2}; + CREATE KEYSPACE simplex WITH replication = {'class': 'NetworkTopologyStrategy', 'dc1': '1', 'dc2': '1'}; USE simplex; CREATE TABLE test (k int, v int, PRIMARY KEY (k, v)); INSERT INTO test (k, v) VALUES (0, 0) @@ -37,7 +37,7 @@ def setup # test_statement_idempotency_on_timeout tests that idempotent statements are retried automatically on the next host. # It first blocks the first host such that it is unreachable. It then attempts a simple SELECT query and verifies that # a Cassandra::Errors::TimeoutError is raised, and the next host is not tried. It finally executes the same query - # once more with idempotent: true and verifies that the statement executes successfully. + # once more with idempotent: true and verifies that the statement executes successfully on another host. # # @expected_errors [Cassandra::Errors::TimeoutError] When a host is unavailable on a non-idempotent query # @@ -45,20 +45,65 @@ def setup # @jira_ticket RUBY-146 # @expected_result Idempotent queries should be retried on the next host automatically # - # @test_assumptions A 2-node Cassandra cluster with RF=2. + # @test_assumptions A 2-dc Cassandra cluster 1 node in each dc. # @test_category queries:timeout # def test_statement_idempotency_on_timeout - cluster = Cassandra.cluster - session = cluster.connect("simplex") + datacenter = 'dc1' + max_remote_hosts_to_use = 1 + policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new(datacenter, max_remote_hosts_to_use) + cluster = Cassandra.cluster(load_balancing_policy: policy) + session = cluster.connect('simplex') - @@ccm_cluster.block_node("node1") + @@ccm_cluster.block_node('node1') assert_raises(Cassandra::Errors::TimeoutError) do - session.execute("SELECT * FROM test", consistency: :one) + session.execute('SELECT * FROM test', consistency: :one) end - session.execute("SELECT * FROM test", consistency: :one, idempotent: true) + info = session.execute('SELECT * FROM test', consistency: :one, idempotent: true).execution_info + assert_equal 2, info.hosts.size + assert_equal '127.0.0.1', info.hosts[0].ip.to_s + assert_equal '127.0.0.2', info.hosts[1].ip.to_s + ensure + @@ccm_cluster.unblock_nodes + cluster && cluster.close + end + + # Test for retrying idempotent statements on timeout + # + # test_statement_idempotency_on_timeout tests that idempotent statements are retried automatically on the next host, + # when the keyspace is not predefined. It first blocks the first host such that it is unreachable. It then attempts a + # simple SELECT query and verifies that a Cassandra::Errors::TimeoutError is raised, and the next host is not tried. + # It finally executes the same query once more with idempotent: true and verifies that the statement executes + # successfully on another host. + # + # @expected_errors [Cassandra::Errors::TimeoutError] When a host is unavailable on a non-idempotent query + # + # @since 3.0.1 + # @jira_ticket RUBY-233 + # @expected_result Idempotent queries should be retried on the next host automatically + # + # @test_assumptions A 2-dc Cassandra cluster 1 node in each dc. + # @test_category queries:timeout + # + def test_statement_idempotency_on_timeout_no_keyspace_predefined + datacenter = 'dc1' + max_remote_hosts_to_use = 1 + policy = Cassandra::LoadBalancing::Policies::DCAwareRoundRobin.new(datacenter, max_remote_hosts_to_use) + cluster = Cassandra.cluster(load_balancing_policy: policy) + session = cluster.connect + + @@ccm_cluster.block_node('node1') + + assert_raises(Cassandra::Errors::TimeoutError) do + session.execute('SELECT * FROM simplex.test', consistency: :one) + end + + info = session.execute('SELECT * FROM simplex.test', consistency: :one, idempotent: true).execution_info + assert_equal 2, info.hosts.size + assert_equal '127.0.0.1', info.hosts[0].ip.to_s + assert_equal '127.0.0.2', info.hosts[1].ip.to_s ensure @@ccm_cluster.unblock_nodes cluster && cluster.close diff --git a/integration/integration_test_case.rb b/integration/integration_test_case.rb index 91651e34e..bd3959539 100644 --- a/integration/integration_test_case.rb +++ b/integration/integration_test_case.rb @@ -29,7 +29,7 @@ class IntegrationTestCase < MiniTest::Unit::TestCase @@ccm_cluster = nil def self.before_suite - @@ccm_cluster = CCM.setup_cluster(1, 1) + @@ccm_cluster = CCM.setup_cluster(1, 1) unless self == IntegrationTestCase end def self.after_suite From 0ff263941fb97f62c6ddb2a5609cb715eed18d3e Mon Sep 17 00:00:00 2001 From: Kishan Karunaratne Date: Fri, 17 Jun 2016 11:23:24 -0700 Subject: [PATCH 094/196] Fix client warning tests (CASSANDRA-10876) --- integration/client_warnings_test.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/integration/client_warnings_test.rb b/integration/client_warnings_test.rb index 12c8f3d21..d1686b47b 100644 --- a/integration/client_warnings_test.rb +++ b/integration/client_warnings_test.rb @@ -37,10 +37,13 @@ def setup CREATE TABLE test (k int, v text, PRIMARY KEY (k, v)); CQL - @query = "BEGIN UNLOGGED BATCH INSERT INTO test (k, v) VALUES (0, '#{'a' * 5 * 1025}') APPLY BATCH" - @exceeding_warning = Regexp.new(/Batch of prepared statements for .* is of size .*, exceeding specified threshold of 5120/) - @partition_warning = Regexp.new(/Unlogged batch covering 2 partitions detected against table .* You should use a logged \ -batch for atomicity, or asynchronous writes for performance/) + @query = "BEGIN UNLOGGED BATCH + INSERT INTO test (k, v) VALUES (0, '#{'a' * 5 * 1025}') + INSERT INTO test (k, v) VALUES (1, '#{'a' * 5 * 1025}') + APPLY BATCH" + @exceeding_warning = Regexp.new(/Batch.* for .* is of size .*, exceeding specified threshold of 5120/) + @partition_warning = Regexp.new(/Unlogged batch covering 2 partitions detected against table .* You should use a \ +logged batch for atomicity, or asynchronous writes for performance/) end end From 07951d3106383934d2b55c24d20f4c3023bdab81 Mon Sep 17 00:00:00 2001 From: Kishan Karunaratne Date: Fri, 17 Jun 2016 13:18:32 -0700 Subject: [PATCH 095/196] Fix client warning feature (CASSANDRA-10876) --- features/debugging/execution_info.feature | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/features/debugging/execution_info.feature b/features/debugging/execution_info.feature index a5c48a36b..f18574929 100644 --- a/features/debugging/execution_info.feature +++ b/features/debugging/execution_info.feature @@ -83,9 +83,11 @@ Feature: Execution information '(id, title, album, artist, tags) values ' + '(?, ?, ?, ?, ?)') - args = [Cassandra::Uuid.new('f6071e72-48ec-4fcb-bf3e-379c8d696488'), 'a' * 5 * 1025, 'a1', 'a1', Set.new(['t1'])] + args0 = [Cassandra::Uuid.new('f6071e72-48ec-4fcb-bf3e-379c8d696488'), 'a' * 5 * 1025, 'a1', 'a1', Set.new(['t1'])] + args1 = [Cassandra::Uuid.new('f6071e72-48ec-4fcb-bf3e-379c8d696498'), 'a' * 5 * 1025, 'a1', 'a1', Set.new(['t1'])] batch = session.unlogged_batch do |b| - b.add(insert, arguments: args) + b.add(insert, arguments: args0) + b.add(insert, arguments: args1) end execution_info = session.execute(batch).execution_info @@ -94,7 +96,7 @@ Feature: Execution information When it is executed Then its output should match: """ - warnings: Batch of prepared statements for .* is of size .*, exceeding specified threshold of 5120 + warnings: Batch.* for .* is of size .*, exceeding specified threshold of 5120 """ Scenario: execution information reflects retry decision From d7236d54fe6ddc283f62b1964e5a4545d1a6c1ee Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 27 Jun 2016 14:30:27 -0700 Subject: [PATCH 096/196] Doc tweaks in preparation of 3.0.1 release. --- CHANGELOG.md | 3 +-- README.md | 36 +++++++++++++++++---------------- features/reconnection/README.md | 2 +- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dedf04279..ab8df43f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,4 @@ -# master -Features: +# 3.0.1 Bug Fixes: * [RUBY-219](https://datastax-oss.atlassian.net/browse/RUBY-219) Sometimes get stack trace in metadata.rb due to failure in SortedSet initialization. diff --git a/README.md b/README.md index 254ece9ef..b5f23aaed 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Datastax Ruby Driver for Apache Cassandra -*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).* +*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.1](https://github.com/datastax/ruby-driver/tree/v3.0.1).* [![Build Status](https://travis-ci.org/datastax/ruby-driver.svg?branch=master)](https://travis-ci.org/datastax/ruby-driver) @@ -8,7 +8,7 @@ A Ruby client driver for Apache Cassandra. This driver works exclusively with the Cassandra Query Language version 3 (CQL3) and Cassandra's native protocol. - Code: https://github.com/datastax/ruby-driver -- Docs: http://datastax.github.io/ruby-driver/ +- Docs: http://docs.datastax.com/en/latest-ruby-driver/ruby-driver/whatsNew.html - Jira: https://datastax-oss.atlassian.net/browse/RUBY - Mailing List: https://groups.google.com/a/lists.datastax.com/forum/#!forum/ruby-driver-user - 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. 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: -* [Asynchronous execution](http://datastax.github.io/ruby-driver/features/asynchronous_io/) -* 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/) -* Automatic peer discovery and cluster metadata with [support for change notifications](http://datastax.github.io/ruby-driver/features/state_listeners/) -* 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/) -* [SSL encryption](http://datastax.github.io/ruby-driver/features/security/ssl_encryption/) -* [Flexible and robust error handling](http://datastax.github.io/ruby-driver/features/error_handling/) -* [Per-request execution information and tracing](http://datastax.github.io/ruby-driver/features/debugging/) -* [Configurable address resolution](http://datastax.github.io/ruby-driver/features/address_resolution/) +* [Asynchronous execution](http://docs.datastax.com/en/developer/ruby-driver/3.0/supplemental/features/asynchronous_io/) +* 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/) +* 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/) +* 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/) +* [SSL encryption](http://docs.datastax.com/en/developer/ruby-driver/3.0/supplemental/features/security/ssl_encryption/) +* [Flexible and robust error handling](http://docs.datastax.com/en/developer/ruby-driver/3.0/supplemental/features/error_handling/) +* [Per-request execution information and tracing](http://docs.datastax.com/en/developer/ruby-driver/3.0/supplemental/features/debugging/) +* [Configurable address resolution](http://docs.datastax.com/en/developer/ruby-driver/3.0/supplemental/features/address_resolution/) [Check out the slides from Ruby Driver Explained](https://speakerdeck.com/avalanche123/ruby-driver-explained) for a detailed overview of the Ruby Driver architecture. @@ -67,9 +67,9 @@ __Note__: The host you specify is just a seed node, the driver will automaticall Read more: -* [`Cassandra.cluster` options](http://datastax.github.io/ruby-driver/api/#cluster-class_method) -* [`Session#execute_async` options](http://datastax.github.io/ruby-driver/api/session/#execute_async-instance_method) -* [Usage documentation](http://datastax.github.io/ruby-driver/features) +* [`Cassandra.cluster` options](http://docs.datastax.com/en/developer/ruby-driver/3.0/supplemental/api/cassandra/#cluster-class_method) +* [`Session#execute_async` options](http://docs.datastax.com/en/developer/ruby-driver/3.0/supplemental/api/cassandra/session/#execute_async-instance_method) +* [Usage documentation](http://docs.datastax.com/en/developer/ruby-driver/3.0/supplemental/features) ## Installation @@ -85,14 +85,16 @@ Install via Gemfile gem 'cassandra-driver' ``` -__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) +__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) ## Upgrading from cql-rb 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. -## What's new in v3.0.0 +## What's new in v3.0 + +See the [changelog](https://github.com/datastax/ruby-driver/blob/master/CHANGELOG.md) for details on patch versions. ### Features: @@ -210,7 +212,7 @@ the release. * Because the driver reactor is using `IO.select`, the maximum number of tcp connections allowed is 1024. * Because the driver uses `IO#write_nonblock`, Windows is not supported. -Please [refer to the usage documentation for more information on common pitfalls](http://datastax.github.io/ruby-driver/features/) +Please [refer to the usage documentation for more information on common pitfalls](http://docs.datastax.com/en/developer/ruby-driver/3.0/supplemental/features/) ## Contributing @@ -240,4 +242,4 @@ License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF either express or implied. See the License for the specific language governing permissions and limitations under the License. - [1]: http://datastax.github.io/ruby-driver/api + [1]: http://docs.datastax.com/en/developer/ruby-driver/3.0/supplemental/api diff --git a/features/reconnection/README.md b/features/reconnection/README.md index 9ae5778f5..d41eb9c8d 100644 --- a/features/reconnection/README.md +++ b/features/reconnection/README.md @@ -1,3 +1,3 @@ # Reconnection -Ruby driver will automatically reestablish failed connections to Cassandra nodes. It uses reconnection policy to determine retry intervals for reconnection. \ No newline at end of file +Ruby driver will automatically reestablish failed connections to Cassandra nodes. It uses a reconnection policy to determine retry intervals for reconnection. \ No newline at end of file From a0fa638ce271e9fdc3d494ec3b682df5eaec25b5 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 27 Jun 2016 14:40:53 -0700 Subject: [PATCH 097/196] More doc tweaks --- features/state_listeners/membership_changes.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/state_listeners/membership_changes.feature b/features/state_listeners/membership_changes.feature index 275bcc0d1..cb462996a 100644 --- a/features/state_listeners/membership_changes.feature +++ b/features/state_listeners/membership_changes.feature @@ -1,4 +1,4 @@ -Feature: membership change detection +Feature: Membership change detection Cluster object allows registering state listeners. It then guarantees that they will be notifies on cluster membership changes. From 645dd7653f7da3ed032e9434a81828e611d4757d Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 27 Jun 2016 14:41:40 -0700 Subject: [PATCH 098/196] Updated docs.yaml for 3.0.1 --- docs.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs.yaml b/docs.yaml index e8a747ecb..49f7e8d4f 100644 --- a/docs.yaml +++ b/docs.yaml @@ -37,8 +37,8 @@ links: - title: Releases href: https://github.com/datastax/ruby-driver/releases versions: - - name: v3.0.0 - ref: 0ef47aaac0253c2cdd23ab487b3a66789416f1cb + - name: v3.0.1 + ref: a0fa638ce271e9fdc3d494ec3b682df5eaec25b5 - name: v2.1.6 ref: 474568501e5ef771a496c3a901f3cbdc3854f512 - name: v2.0.1 From 52715e01d32953b7f18783189ca0c7a8eab51568 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 27 Jun 2016 15:21:21 -0700 Subject: [PATCH 099/196] Updated CHANGELOG to mention RUBY-220 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab8df43f1..1d183764b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ Bug Fixes: * [RUBY-219](https://datastax-oss.atlassian.net/browse/RUBY-219) Sometimes get stack trace in metadata.rb due to failure in SortedSet initialization. +* [RUBY-220](https://datastax-oss.atlassian.net/browse/RUBY-220) Improve support for custom types. * [RUBY-231](https://datastax-oss.atlassian.net/browse/RUBY-231) Driver ignores explicitly specified nil timeout (to indicate no time limit on query execution). * [RUBY-233](https://datastax-oss.atlassian.net/browse/RUBY-233) Client timeout errors are retried for non-idempotent statements. From 045fb63bd482b9346d939f943430e94f0c339813 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 27 Jun 2016 15:24:49 -0700 Subject: [PATCH 100/196] Changed file references to point to files in 3.0.1 tag --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b5f23aaed..516f29fa8 100644 --- a/README.md +++ b/README.md @@ -90,11 +90,11 @@ __Note__: if you want to use compression you should also install [snappy](http:/ ## Upgrading from cql-rb -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. +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.1/examples/cql-rb-wrapper.rb) to assist you with gradual upgrade. ## What's new in v3.0 -See the [changelog](https://github.com/datastax/ruby-driver/blob/master/CHANGELOG.md) for details on patch versions. +See the [changelog](https://github.com/datastax/ruby-driver/blob/v3.0.1/CHANGELOG.md) for details on patch versions. ### Features: @@ -164,7 +164,7 @@ examples in the `features/` directory. ## Running tests If you don't feel like reading through the following instructions on how to run -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). +ruby-driver tests, feel free to [check out .travis.yml for the entire build code](https://github.com/datastax/ruby-driver/blob/v3.0.1/.travis.yml). * Check out the driver codebase and install test dependencies: @@ -188,7 +188,7 @@ CASSANDRA_VERSION=2.0.17 bundle exec rake test # run both as well as integration ## Changelog & versioning Check out the [releases on GitHub](https://github.com/datastax/ruby-driver/releases) and -[changelog](https://github.com/datastax/ruby-driver/blob/master/CHANGELOG.md). Version +[changelog](https://github.com/datastax/ruby-driver/blob/v3.0.1/CHANGELOG.md). Version numbering follows the [semantic versioning](http://semver.org/) scheme. Private and experimental APIs, defined as whatever is not in the From cfb16c071f98ad5e0ad7fc5fc68cbf0ba23b3c1d Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 27 Jun 2016 15:26:35 -0700 Subject: [PATCH 101/196] Update docs.yaml to point to new 3.0.1 sha --- docs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs.yaml b/docs.yaml index 49f7e8d4f..cc36e0528 100644 --- a/docs.yaml +++ b/docs.yaml @@ -38,7 +38,7 @@ links: href: https://github.com/datastax/ruby-driver/releases versions: - name: v3.0.1 - ref: a0fa638ce271e9fdc3d494ec3b682df5eaec25b5 + ref: 045fb63bd482b9346d939f943430e94f0c339813 - name: v2.1.6 ref: 474568501e5ef771a496c3a901f3cbdc3854f512 - name: v2.0.1 From 17bd57774fdb2b6d233fed99809eb972cbf29b2c Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 27 Jun 2016 15:43:38 -0700 Subject: [PATCH 102/196] Bump version to 3.0.2. --- CHANGELOG.md | 2 +- Gemfile.lock | 2 +- README.md | 10 +++++----- lib/cassandra/version.rb | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d183764b..f78695a85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# 3.0.1 +# 3.0.2 Bug Fixes: * [RUBY-219](https://datastax-oss.atlassian.net/browse/RUBY-219) Sometimes get stack trace in metadata.rb due to failure in SortedSet initialization. diff --git a/Gemfile.lock b/Gemfile.lock index e3531d49c..e72ccff91 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - cassandra-driver (3.0.1) + cassandra-driver (3.0.2) ione (~> 1.2) GEM diff --git a/README.md b/README.md index 516f29fa8..37d58dbfc 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Datastax Ruby Driver for Apache Cassandra -*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.1](https://github.com/datastax/ruby-driver/tree/v3.0.1).* +*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).* [![Build Status](https://travis-ci.org/datastax/ruby-driver.svg?branch=master)](https://travis-ci.org/datastax/ruby-driver) @@ -90,11 +90,11 @@ __Note__: if you want to use compression you should also install [snappy](http:/ ## Upgrading from cql-rb -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.1/examples/cql-rb-wrapper.rb) to assist you with gradual upgrade. +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. ## What's new in v3.0 -See the [changelog](https://github.com/datastax/ruby-driver/blob/v3.0.1/CHANGELOG.md) for details on patch versions. +See the [changelog](https://github.com/datastax/ruby-driver/blob/v3.0.2/CHANGELOG.md) for details on patch versions. ### Features: @@ -164,7 +164,7 @@ examples in the `features/` directory. ## Running tests If you don't feel like reading through the following instructions on how to run -ruby-driver tests, feel free to [check out .travis.yml for the entire build code](https://github.com/datastax/ruby-driver/blob/v3.0.1/.travis.yml). +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). * Check out the driver codebase and install test dependencies: @@ -188,7 +188,7 @@ CASSANDRA_VERSION=2.0.17 bundle exec rake test # run both as well as integration ## Changelog & versioning Check out the [releases on GitHub](https://github.com/datastax/ruby-driver/releases) and -[changelog](https://github.com/datastax/ruby-driver/blob/v3.0.1/CHANGELOG.md). Version +[changelog](https://github.com/datastax/ruby-driver/blob/v3.0.2/CHANGELOG.md). Version numbering follows the [semantic versioning](http://semver.org/) scheme. Private and experimental APIs, defined as whatever is not in the diff --git a/lib/cassandra/version.rb b/lib/cassandra/version.rb index 3a04c0bbe..616a19de3 100644 --- a/lib/cassandra/version.rb +++ b/lib/cassandra/version.rb @@ -17,5 +17,5 @@ #++ module Cassandra - VERSION = '3.0.1'.freeze + VERSION = '3.0.2'.freeze end From 654882d5b0a8e4280e71be4c3fd495873aa257b1 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 27 Jun 2016 15:45:01 -0700 Subject: [PATCH 103/196] Updated docs.yaml for 3.0.2 --- docs.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs.yaml b/docs.yaml index cc36e0528..4ebe52412 100644 --- a/docs.yaml +++ b/docs.yaml @@ -37,8 +37,8 @@ links: - title: Releases href: https://github.com/datastax/ruby-driver/releases versions: - - name: v3.0.1 - ref: 045fb63bd482b9346d939f943430e94f0c339813 + - name: v3.0.2 + ref: 17bd57774fdb2b6d233fed99809eb972cbf29b2c - name: v2.1.6 ref: 474568501e5ef771a496c3a901f3cbdc3854f512 - name: v2.0.1 From 139fdf5fe7691cc0a67e5c74c62777ff0297e65b Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 27 Jun 2016 16:54:22 -0700 Subject: [PATCH 104/196] Update CHANGELOG and README to set the stage for the next release of the driver. --- CHANGELOG.md | 6 ++++++ README.md | 8 ++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f78695a85..969700f5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# master + +Features: + +Bug Fixes: + # 3.0.2 Bug Fixes: diff --git a/README.md b/README.md index 37d58dbfc..dbd5b7447 100644 --- a/README.md +++ b/README.md @@ -90,11 +90,11 @@ __Note__: if you want to use compression you should also install [snappy](http:/ ## Upgrading from cql-rb -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. +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. ## What's new in v3.0 -See the [changelog](https://github.com/datastax/ruby-driver/blob/v3.0.2/CHANGELOG.md) for details on patch versions. +See the [changelog](https://github.com/datastax/ruby-driver/blob/master/CHANGELOG.md) for details on patch versions. ### Features: @@ -164,7 +164,7 @@ examples in the `features/` directory. ## Running tests If you don't feel like reading through the following instructions on how to run -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). +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). * Check out the driver codebase and install test dependencies: @@ -188,7 +188,7 @@ CASSANDRA_VERSION=2.0.17 bundle exec rake test # run both as well as integration ## Changelog & versioning Check out the [releases on GitHub](https://github.com/datastax/ruby-driver/releases) and -[changelog](https://github.com/datastax/ruby-driver/blob/v3.0.2/CHANGELOG.md). Version +[changelog](https://github.com/datastax/ruby-driver/blob/master/CHANGELOG.md). Version numbering follows the [semantic versioning](http://semver.org/) scheme. Private and experimental APIs, defined as whatever is not in the From c94a87ee187ee5fc79fc30ee31cb7b36e06bd1a8 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Fri, 8 Jul 2016 17:45:49 -0700 Subject: [PATCH 105/196] RUBY-241 - Don't expose materialized view's with nil base-table's. * Change base_table instance variable into a function that looks up the base-table-name in the keyspace, thus being resilient to metadata updates where view metadata comes in before the corresponding table. * Update keyspace's view-related methods to only operate on / return views whose base table exists in the schema. --- CHANGELOG.md | 4 ++ lib/cassandra/cluster/schema/fetchers.rb | 11 ++- lib/cassandra/keyspace.rb | 16 +++-- lib/cassandra/materialized_view.rb | 16 +++-- spec/cassandra/keyspace_spec.rb | 91 ++++++++++++++++++++++++ spec/cassandra/materialized_view_spec.rb | 11 +-- 6 files changed, 126 insertions(+), 23 deletions(-) create mode 100644 spec/cassandra/keyspace_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 969700f5f..0b1bdad2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ Features: Bug Fixes: +# 3.0.3 + +Bug Fixes: +* [RUBY-241](https://datastax-oss.atlassian.net/browse/RUBY-241) Materialied views sometimes have nil ref to base-table. # 3.0.2 diff --git a/lib/cassandra/cluster/schema/fetchers.rb b/lib/cassandra/cluster/schema/fetchers.rb index 8873eab8d..4c2a46ca6 100644 --- a/lib/cassandra/cluster/schema/fetchers.rb +++ b/lib/cassandra/cluster/schema/fetchers.rb @@ -107,10 +107,8 @@ def fetch_materialized_view(connection, keyspace_name, view_name) nil else view_row = rows_views.first - base_table = @schema.keyspace(keyspace_name).table(view_row['base_table_name']) create_materialized_view(view_row, - rows_columns, - base_table) + rows_columns) end end end @@ -1216,10 +1214,8 @@ def create_keyspace(keyspace_data, rows_tables, rows_columns, rows_types, views = rows_views.each_with_object({}) do |row, h| view_name = row['view_name'] - base_table = tables[row['base_table_name']] h[view_name] = create_materialized_view(row, lookup_columns[view_name], - base_table, types) end @@ -1366,9 +1362,10 @@ def create_index(table, row_index) options['target'], options)) end - def create_materialized_view(view_data, rows_columns, base_table, types = nil) + def create_materialized_view(view_data, rows_columns, types = nil) keyspace_name = view_data['keyspace_name'] view_name = view_data['view_name'] + base_table_name = view_data['base_table_name'] include_all_columns = view_data['include_all_columns'] where_clause = view_data['where_clause'] @@ -1406,7 +1403,7 @@ def create_materialized_view(view_data, rows_columns, base_table, types = nil) view_options, include_all_columns, where_clause, - base_table, + base_table_name, view_data['id']) end end diff --git a/lib/cassandra/keyspace.rb b/lib/cassandra/keyspace.rb index 81363fd9b..9ce60bbc7 100644 --- a/lib/cassandra/keyspace.rb +++ b/lib/cassandra/keyspace.rb @@ -113,13 +113,15 @@ def each_table(&block) # @return [Boolean] whether this keyspace has a materialized view with the given name # @param name [String] materialized view name def has_materialized_view?(name) - @views.key?(name) + # We check if the view exists *and* that its base-table is set. If base-table isn't available, + # it will be soon, so the user can poll on this method until we return a fully-baked materialized view. + @views.key?(name) && @views[name].base_table end # @return [Cassandra::MaterializedView, nil] a materialized view or nil # @param name [String] materialized view name def materialized_view(name) - @views[name] + @views[name] if has_materialized_view?(name) end # Yield or enumerate each materialized view defined in this keyspace @@ -130,10 +132,16 @@ def materialized_view(name) # @return [Array] a list of materialized views def each_materialized_view(&block) if block_given? - @views.each_value(&block) + @views.each_value do |v| + block.call(v) if v.base_table + end self else - @views.values + result = [] + @views.each_value do |v| + result << v if v.base_table + end + result end end alias materialized_views each_materialized_view diff --git a/lib/cassandra/materialized_view.rb b/lib/cassandra/materialized_view.rb index b0330954b..4632a1218 100644 --- a/lib/cassandra/materialized_view.rb +++ b/lib/cassandra/materialized_view.rb @@ -21,9 +21,6 @@ module Cassandra # @see Cassandra::Keyspace#each_materialized_view # @see Cassandra::Keyspace#materialized_view class MaterializedView < ColumnContainer - # @return [Table] the table that this materialized view applies to. - attr_reader :base_table - # @private def initialize(keyspace, name, @@ -33,12 +30,17 @@ def initialize(keyspace, options, include_all_columns, where_clause, - base_table, + base_table_name, id) super(keyspace, name, partition_key, clustering_columns, other_columns, options, id) @include_all_columns = include_all_columns @where_clause = where_clause - @base_table = base_table + @base_table_name = base_table_name + end + + # @return [Table] the table that this materialized view applies to. + def base_table + @keyspace.table(@base_table_name) end # @return [String] a cql representation of this materialized view @@ -52,7 +54,7 @@ def to_cql Util.escape_name(column.name) end.join(', ') end - cql << "\nFROM #{keyspace_name}.#{Util.escape_name(@base_table.name)}" + cql << "\nFROM #{keyspace_name}.#{Util.escape_name(@base_table_name)}" cql << "\nWHERE #{@where_clause}" if @where_clause cql << "\nPRIMARY KEY ((" cql << @partition_key.map do |column| @@ -74,7 +76,7 @@ def eql?(other) super.eql?(other) && @include_all_columns == other.include_all_columns && @where_clause == other.where_clause && - @base_table == other.base_table + @base_table_name == other.base_table.name end alias == eql? diff --git a/spec/cassandra/keyspace_spec.rb b/spec/cassandra/keyspace_spec.rb new file mode 100644 index 000000000..360446544 --- /dev/null +++ b/spec/cassandra/keyspace_spec.rb @@ -0,0 +1,91 @@ +# encoding: utf-8 + +#-- +# Copyright 2013-2016 DataStax, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + +require 'spec_helper' + +include Cassandra::Types +module Cassandra + describe(Keyspace) do + let(:view) { double('view') } + let(:table) { double('table') } + let(:ks) { Keyspace.new('myks', true, nil, {'mytable' => table}, nil, nil, nil, {'myview' => view}) } + + before do + allow(view).to receive(:set_keyspace) + allow(table).to receive(:set_keyspace) + end + + context :has_materialized_view? do + it 'should return true if the view exists and has a base-table' do + expect(view).to receive(:base_table).and_return(:table) + expect(ks.has_materialized_view?('myview')).to be_truthy + end + + it 'should return false if the view exists but does not have a base-table' do + expect(view).to receive(:base_table).and_return(nil) + expect(ks.has_materialized_view?('myview')).to be_falsey + end + end + + context :materialized_view do + it 'should return the view if it exists and has a base-table' do + expect(view).to receive(:base_table).and_return(:table) + expect(ks.materialized_view('myview')).to be(view) + end + + it 'should return nil if the view exists but does not have a base-table' do + expect(view).to receive(:base_table).and_return(nil) + expect(ks.materialized_view('myview')).to be_nil + end + end + + context :materialized_views do + it 'should return the view if it exists and has a base-table' do + expect(view).to receive(:base_table).and_return(:table) + expect(ks.materialized_views).to eq([view]) + end + + it 'should not return view if the view exists but does not have a base-table' do + expect(view).to receive(:base_table).and_return(nil) + expect(ks.materialized_views).to be_empty + end + end + + context :each_materialized_view do + it 'should return the view if it exists and has a base-table' do + expect(view).to receive(:base_table).and_return(:table) + result = [] + ks.each_materialized_view do |v| + result << v + end + + expect(result).to eq([view]) + end + + it 'should not return view if the view exists but does not have a base-table' do + expect(view).to receive(:base_table).and_return(nil) + result = [] + ks.each_materialized_view do |v| + result << v + end + + expect(result).to be_empty + end + end + end +end diff --git a/spec/cassandra/materialized_view_spec.rb b/spec/cassandra/materialized_view_spec.rb index f9e7347fb..3c6362c44 100644 --- a/spec/cassandra/materialized_view_spec.rb +++ b/spec/cassandra/materialized_view_spec.rb @@ -34,6 +34,7 @@ module Cassandra before do allow(ks).to receive(:name).and_return('myks1') + allow(ks).to receive(:table).with('table1').and_return(table) allow(table).to receive(:name).and_return('table1') allow(col).to receive(:name).and_return('col') allow(col).to receive(:type).and_return(Cassandra::Types.int) @@ -45,7 +46,7 @@ module Cassandra end it 'should quote keyspace, view name, table name, columns properly' do - t = MaterializedView.new(ks, 'myview1', [col], [], [col2, col3], options, false, where, table,id) + t = MaterializedView.new(ks, 'myview1', [col], [], [col2, col3], options, false, where, 'table1', id) expected_cql = <<-EOF CREATE MATERIALIZED VIEW "myks1"."myview1" AS SELECT col, "col2", "from" @@ -58,7 +59,7 @@ module Cassandra end it 'should quote primary key properly for simple partition key' do - t = MaterializedView.new(ks, 'myview1', [col], [col2], [col3], options, false, where, table,id) + t = MaterializedView.new(ks, 'myview1', [col], [col2], [col3], options, false, where, 'table1', id) expected_cql = <<-EOF CREATE MATERIALIZED VIEW "myks1"."myview1" AS SELECT col, "col2", "from" @@ -71,7 +72,7 @@ module Cassandra end it 'should quote primary key properly for composite partition key' do - t = MaterializedView.new(ks, 'myview1', [col, col2], [col3], [], options, false, where, table,id) + t = MaterializedView.new(ks, 'myview1', [col, col2], [col3], [], options, false, where, 'table1', id) expected_cql = <<-EOF CREATE MATERIALIZED VIEW "myks1"."myview1" AS SELECT col, "col2", "from" @@ -84,7 +85,7 @@ module Cassandra end it 'should handle no where-clause properly' do - t = MaterializedView.new(ks, 'myview1', [col, col2], [col3], [], options, false, nil, table,id) + t = MaterializedView.new(ks, 'myview1', [col, col2], [col3], [], options, false, nil, 'table1', id) expected_cql = <<-EOF CREATE MATERIALIZED VIEW "myks1"."myview1" AS SELECT col, "col2", "from" @@ -96,7 +97,7 @@ module Cassandra end it 'should handle include-all-columns properly' do - t = MaterializedView.new(ks, 'myview1', [col, col2], [col3], [], options, true, nil, table,id) + t = MaterializedView.new(ks, 'myview1', [col, col2], [col3], [], options, true, nil, 'table1', id) expected_cql = <<-EOF CREATE MATERIALIZED VIEW "myks1"."myview1" AS SELECT * From 5bdb14b34f6994d78b79f674ddec6aa0a5083733 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Thu, 14 Jul 2016 10:51:13 -0700 Subject: [PATCH 106/196] DOC-1135 - Update custom payload doc ref to point to docs.datastax.com --- lib/cassandra/execution/options.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cassandra/execution/options.rb b/lib/cassandra/execution/options.rb index 36365f78d..d0499e053 100644 --- a/lib/cassandra/execution/options.rb +++ b/lib/cassandra/execution/options.rb @@ -54,7 +54,7 @@ class Options # # @see https://github.com/apache/cassandra/blob/cassandra-3.4/doc/native_protocol_v4.spec#L125-L131 Description # of custom payload in Cassandra native protocol v4. - # @see https://datastax.github.io/java-driver/manual/custom_payloads/#enabling-custom-payloads-on-c-nodes + # @see http://docs.datastax.com/en/developer/java-driver/3.0/supplemental/manual/custom_payloads/?local=true&nav=toc#enabling-custom-payloads-on-c-nodes # Enabling custom payloads on Cassandra nodes. # # @example Sending a custom payload From 1d2bce7bb17da42d109bd39a1d34ea823921263d Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Fri, 15 Jul 2016 16:14:13 -0700 Subject: [PATCH 107/196] Updated version to 3.0.3.rc.1. --- lib/cassandra/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cassandra/version.rb b/lib/cassandra/version.rb index 616a19de3..4e67805b4 100644 --- a/lib/cassandra/version.rb +++ b/lib/cassandra/version.rb @@ -17,5 +17,5 @@ #++ module Cassandra - VERSION = '3.0.2'.freeze + VERSION = '3.0.3.rc.1'.freeze end From 510b7943cdde6c14448da4e7a5ced33382bae712 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Sun, 17 Jul 2016 10:52:05 -0700 Subject: [PATCH 108/196] Updated version to 3.0.3.rc.1. --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index e72ccff91..152801ed8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - cassandra-driver (3.0.2) + cassandra-driver (3.0.3.rc.1) ione (~> 1.2) GEM From 0c7dd958d0bdfa71033ee6ad260eb64b04ebc622 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 18 Jul 2016 16:12:54 -0700 Subject: [PATCH 109/196] RUBY-241 - MaterializedView base_table returns nil sometimes * Fixed subtle bug in setting the keyspace attribute of a view; it would only be set if the view didn't have a keyspace initially. However, as schema updates occur, views and tables can transfer from one keyspace to a new version of the keyspace. --- lib/cassandra/column_container.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/cassandra/column_container.rb b/lib/cassandra/column_container.rb index c8ae29269..6e79a393d 100644 --- a/lib/cassandra/column_container.rb +++ b/lib/cassandra/column_container.rb @@ -286,13 +286,12 @@ def each_column(&block) # @private # keyspace attribute may be nil because when this object was constructed, we didn't have - # its keyspace constructed yet. So allow updating @keyspace if it's nil, thus + # its keyspace constructed yet. So allow updating @keyspace, thus # allowing fetchers to create keyspace, table/view, and hook them together without # worrying about chickens and eggs. - # NOTE: Ignore the set request if the @keyspace is already set. # rubocop:disable Style/AccessorMethodName def set_keyspace(keyspace) - @keyspace = keyspace unless @keyspace + @keyspace = keyspace end # @private From 50603a755aebf690a5612a0be8faa0ea688e8fa5 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 18 Jul 2016 17:49:13 -0700 Subject: [PATCH 110/196] Fixed Keyspace#has_materialized_view to always return a boolean. --- lib/cassandra/keyspace.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cassandra/keyspace.rb b/lib/cassandra/keyspace.rb index 9ce60bbc7..f45a5eeeb 100644 --- a/lib/cassandra/keyspace.rb +++ b/lib/cassandra/keyspace.rb @@ -115,7 +115,7 @@ def each_table(&block) def has_materialized_view?(name) # We check if the view exists *and* that its base-table is set. If base-table isn't available, # it will be soon, so the user can poll on this method until we return a fully-baked materialized view. - @views.key?(name) && @views[name].base_table + @views.key?(name) && !@views[name].base_table.nil? end # @return [Cassandra::MaterializedView, nil] a materialized view or nil From 6ec356a9c2082a4d1ae687e16385f34a56269f46 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Fri, 22 Jul 2016 09:31:08 -0700 Subject: [PATCH 111/196] Bump version to 3.0.3, prepare for 3.0.3 release --- CHANGELOG.md | 5 ----- Gemfile.lock | 2 +- README.md | 10 +++++----- lib/cassandra/version.rb | 2 +- 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b1bdad2d..9ff6508ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,3 @@ -# master - -Features: - -Bug Fixes: # 3.0.3 Bug Fixes: diff --git a/Gemfile.lock b/Gemfile.lock index 152801ed8..cc35eff45 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - cassandra-driver (3.0.3.rc.1) + cassandra-driver (3.0.3) ione (~> 1.2) GEM diff --git a/README.md b/README.md index dbd5b7447..16f8300a1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Datastax Ruby Driver for Apache Cassandra -*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).* +*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.3](https://github.com/datastax/ruby-driver/tree/v3.0.3).* [![Build Status](https://travis-ci.org/datastax/ruby-driver.svg?branch=master)](https://travis-ci.org/datastax/ruby-driver) @@ -90,11 +90,11 @@ __Note__: if you want to use compression you should also install [snappy](http:/ ## Upgrading from cql-rb -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. +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.3/examples/cql-rb-wrapper.rb) to assist you with gradual upgrade. ## What's new in v3.0 -See the [changelog](https://github.com/datastax/ruby-driver/blob/master/CHANGELOG.md) for details on patch versions. +See the [changelog](https://github.com/datastax/ruby-driver/blob/v3.0.3/CHANGELOG.md) for details on patch versions. ### Features: @@ -164,7 +164,7 @@ examples in the `features/` directory. ## Running tests If you don't feel like reading through the following instructions on how to run -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). +ruby-driver tests, feel free to [check out .travis.yml for the entire build code](https://github.com/datastax/ruby-driver/blob/v3.0.3/.travis.yml). * Check out the driver codebase and install test dependencies: @@ -188,7 +188,7 @@ CASSANDRA_VERSION=2.0.17 bundle exec rake test # run both as well as integration ## Changelog & versioning Check out the [releases on GitHub](https://github.com/datastax/ruby-driver/releases) and -[changelog](https://github.com/datastax/ruby-driver/blob/master/CHANGELOG.md). Version +[changelog](https://github.com/datastax/ruby-driver/blob/v3.0.3/CHANGELOG.md). Version numbering follows the [semantic versioning](http://semver.org/) scheme. Private and experimental APIs, defined as whatever is not in the diff --git a/lib/cassandra/version.rb b/lib/cassandra/version.rb index 4e67805b4..62452ca24 100644 --- a/lib/cassandra/version.rb +++ b/lib/cassandra/version.rb @@ -17,5 +17,5 @@ #++ module Cassandra - VERSION = '3.0.3.rc.1'.freeze + VERSION = '3.0.3'.freeze end From efdc7174c16f26e1e3f85213a999e7b5900f0385 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Fri, 22 Jul 2016 09:35:09 -0700 Subject: [PATCH 112/196] Updated docs.yaml for 3.0.3 --- docs.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs.yaml b/docs.yaml index 4ebe52412..aa4ae0ff4 100644 --- a/docs.yaml +++ b/docs.yaml @@ -37,8 +37,8 @@ links: - title: Releases href: https://github.com/datastax/ruby-driver/releases versions: - - name: v3.0.2 - ref: 17bd57774fdb2b6d233fed99809eb972cbf29b2c + - name: v3.0.3 + ref: 6ec356a9c2082a4d1ae687e16385f34a56269f46 - name: v2.1.6 ref: 474568501e5ef771a496c3a901f3cbdc3854f512 - name: v2.0.1 From 768162355fa2ddbcc6e23b06614bd5c9a1d1965e Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Fri, 22 Jul 2016 10:16:26 -0700 Subject: [PATCH 113/196] Bumped version to 3.0.4.rc.1 as the "post-3.0.3" place-holder version. Added new "master" section to CHANGELOG. Updated blob/... refs in README to point to master again. --- CHANGELOG.md | 5 +++++ README.md | 8 ++++---- lib/cassandra/version.rb | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ff6508ed..15ed15923 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# master +Features: + +Bug Fixes: + # 3.0.3 Bug Fixes: diff --git a/README.md b/README.md index 16f8300a1..defb56151 100644 --- a/README.md +++ b/README.md @@ -90,11 +90,11 @@ __Note__: if you want to use compression you should also install [snappy](http:/ ## Upgrading from cql-rb -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.3/examples/cql-rb-wrapper.rb) to assist you with gradual upgrade. +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. ## What's new in v3.0 -See the [changelog](https://github.com/datastax/ruby-driver/blob/v3.0.3/CHANGELOG.md) for details on patch versions. +See the [changelog](https://github.com/datastax/ruby-driver/blob/master/CHANGELOG.md) for details on patch versions. ### Features: @@ -164,7 +164,7 @@ examples in the `features/` directory. ## Running tests If you don't feel like reading through the following instructions on how to run -ruby-driver tests, feel free to [check out .travis.yml for the entire build code](https://github.com/datastax/ruby-driver/blob/v3.0.3/.travis.yml). +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). * Check out the driver codebase and install test dependencies: @@ -188,7 +188,7 @@ CASSANDRA_VERSION=2.0.17 bundle exec rake test # run both as well as integration ## Changelog & versioning Check out the [releases on GitHub](https://github.com/datastax/ruby-driver/releases) and -[changelog](https://github.com/datastax/ruby-driver/blob/v3.0.3/CHANGELOG.md). Version +[changelog](https://github.com/datastax/ruby-driver/blob/master/CHANGELOG.md). Version numbering follows the [semantic versioning](http://semver.org/) scheme. Private and experimental APIs, defined as whatever is not in the diff --git a/lib/cassandra/version.rb b/lib/cassandra/version.rb index 62452ca24..664fa4ab1 100644 --- a/lib/cassandra/version.rb +++ b/lib/cassandra/version.rb @@ -17,5 +17,5 @@ #++ module Cassandra - VERSION = '3.0.3'.freeze + VERSION = '3.0.4.rc.1'.freeze end From 2276130cd41fc9152b68419544389f9313a82255 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Fri, 22 Jul 2016 11:36:21 -0700 Subject: [PATCH 114/196] Update Gemfile.lock as part of bumping version to 3.0.4.rc.1 --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index cc35eff45..dfe8bb361 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - cassandra-driver (3.0.3) + cassandra-driver (3.0.4.rc.1) ione (~> 1.2) GEM From 1d24abe8f86ab571c20263df316af295d66bb455 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Tue, 2 Aug 2016 11:40:57 -0700 Subject: [PATCH 115/196] Added top-level .nav file so that generated docs TOC has "Usage" before "API docs" section. --- .nav | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .nav diff --git a/.nav b/.nav new file mode 100644 index 000000000..55921b5e3 --- /dev/null +++ b/.nav @@ -0,0 +1,2 @@ +features +api From 56978cf214bb53c50371a5b3e5fea3607c87cf36 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Fri, 26 Aug 2016 13:49:25 -0700 Subject: [PATCH 116/196] RUBY-255 - Fix bug in peer_ip calculation. Fold in some extra cases that the Java driver accounts for. * For 3.1 release. --- lib/cassandra/cluster/control_connection.rb | 29 +++- .../cluster/control_connection_spec.rb | 164 +++++++++++++++++- 2 files changed, 187 insertions(+), 6 deletions(-) diff --git a/lib/cassandra/cluster/control_connection.rb b/lib/cassandra/cluster/control_connection.rb index fcbed87bc..73b0d37ab 100644 --- a/lib/cassandra/cluster/control_connection.rb +++ b/lib/cassandra/cluster/control_connection.rb @@ -445,7 +445,7 @@ def refresh_peers_async peers.shuffle! peers.each do |data| - ip = peer_ip(data) + ip = peer_ip(data, connection.host) next unless ip ips << ip @registry.host_found(ip, data) @@ -665,9 +665,30 @@ def connect_to_host(host) @connector.connect(host) end - def peer_ip(data) - ip = data['rpc_address'] - ip = data['peer'] if ip == '0.0.0.0' + def peer_ip(data, host_address) + peer = data['peer'] + + return nil unless peer + + rpc_address = data['rpc_address'] + + if rpc_address.nil? + @logger.info("The system.peers row for '#{data['peer']}' has no rpc_address. This is likely " + + 'a gossip or snitch issue. This host will be ignored.') + return nil + end + + if peer == host_address || rpc_address == host_address + # Some DSE versions were inserting a line for the local node in peers (with mostly null values). + # This has been fixed, but if we detect that's the case, ignore it as it's not really a big deal. + + @logger.debug("System.peers on node #{host_address} has a line for itself. This is not normal but is a " + + 'known problem of some DSE versions. Ignoring the entry.') + return nil + end + + ip = rpc_address + ip = peer if ip == '0.0.0.0' @address_resolver.resolve(ip) end diff --git a/spec/cassandra/cluster/control_connection_spec.rb b/spec/cassandra/cluster/control_connection_spec.rb index 81d94aed1..3ea47a7fe 100644 --- a/spec/cassandra/cluster/control_connection_spec.rb +++ b/spec/cassandra/cluster/control_connection_spec.rb @@ -325,12 +325,22 @@ def handle_request(&handler) it 'skips empty peers' do additional_rpc_addresses = additional_nodes.dup + expect(address_resolution_policy).to receive(:resolve).with(additional_rpc_addresses[0]).and_return(additional_rpc_addresses[0]) + expect(address_resolution_policy).to receive(:resolve).with(additional_rpc_addresses[1]).and_return(additional_rpc_addresses[1]) + + # RUBY-255: We should never try to do address resolution of nil. + expect(address_resolution_policy).to_not receive(:resolve).with(nil) + + # We should give up on a peer if 'peer' is empty. This is indicated by *not* doing + # an address resolution of rpc-address. + expect(address_resolution_policy).to_not receive(:resolve).with('127.1.2.3') + handle_request do |request| case request when Protocol::QueryRequest case request.cql when /FROM system\.peers/ - rows = min_peers[0].times.map do |host_id| + rows = min_peers[0].times.map do ip = additional_rpc_addresses.shift { 'peer' => ip, @@ -347,7 +357,7 @@ def handle_request(&handler) 'rack' => nil, 'data_center' => nil, 'host_id' => nil, - 'rpc_address' => nil, + 'rpc_address' => '127.1.2.3', 'release_version' => nil } Protocol::RowsResultResponse.new(nil, nil, rows, peer_metadata, nil, nil) @@ -361,6 +371,156 @@ def handle_request(&handler) end end + context 'with empty rpc_address' do + it 'skips empty rpc_address' do + additional_rpc_addresses = additional_nodes.dup + + expect(address_resolution_policy).to receive(:resolve).with(additional_rpc_addresses[0]).and_return(additional_rpc_addresses[0]) + expect(address_resolution_policy).to receive(:resolve).with(additional_rpc_addresses[1]).and_return(additional_rpc_addresses[1]) + + # RUBY-255: We should never try to do address resolution of nil. + expect(address_resolution_policy).to_not receive(:resolve).with(nil) + + # We should give up on a peer if 'rpc-address' is empty. This is indicated by *not* doing + # an address resolution of peer. + expect(address_resolution_policy).to_not receive(:resolve).with('127.1.2.3') + + handle_request do |request| + case request + when Protocol::QueryRequest + case request.cql + when /FROM system\.peers/ + rows = min_peers[0].times.map do + ip = additional_rpc_addresses.shift + { + 'peer' => ip, + 'rack' => racks[ip], + 'data_center' => data_centers[ip], + 'host_id' => host_ids[ip], + 'rpc_address' => bind_all_rpc_addresses ? ::IPAddr.new('0.0.0.0') : ip, + 'release_version' => release_versions[ip] + } + end + + rows << { + 'peer' => '127.1.2.3', + 'rack' => nil, + 'data_center' => nil, + 'host_id' => nil, + 'rpc_address' => nil, + 'release_version' => nil + } + Protocol::RowsResultResponse.new(nil, nil, rows, peer_metadata, nil, nil) + end + end + end + + control_connection.connect_async.value + + expect(cluster_registry).to have(3).hosts + end + end + + context 'with local node' do + it 'skips matching peer' do + additional_rpc_addresses = additional_nodes.dup + + expect(address_resolution_policy).to receive(:resolve).with(additional_rpc_addresses[0]).and_return(additional_rpc_addresses[0]) + expect(address_resolution_policy).to receive(:resolve).with(additional_rpc_addresses[1]).and_return(additional_rpc_addresses[1]) + + # RUBY-255: We should never try to do address resolution of nil. + expect(address_resolution_policy).to_not receive(:resolve).with(nil) + + # We should give up on a peer if its rpc_address is the local host. This is indicated by *not* doing + # an address resolution of it. + expect(address_resolution_policy).to_not receive(:resolve).with('127.0.0.9') + expect(address_resolution_policy).to_not receive(:resolve).with('127.0.0.1') + + handle_request do |request| + case request + when Protocol::QueryRequest + case request.cql + when /FROM system\.peers/ + rows = min_peers[0].times.map do + ip = additional_rpc_addresses.shift + { + 'peer' => ip, + 'rack' => racks[ip], + 'data_center' => data_centers[ip], + 'host_id' => host_ids[ip], + 'rpc_address' => bind_all_rpc_addresses ? IPAddr.new('0.0.0.0') : ip, + 'release_version' => release_versions[ip] + } + end + + rows << { + 'peer' => connections.first.host, + 'rack' => nil, + 'data_center' => nil, + 'host_id' => nil, + 'rpc_address' => '127.0.0.9', + 'release_version' => nil + } + Protocol::RowsResultResponse.new(nil, nil, rows, peer_metadata, nil, nil) + end + end + end + + control_connection.connect_async.value + + expect(cluster_registry).to have(3).hosts + end + + it 'skips matching rpc_address' do + additional_rpc_addresses = additional_nodes.dup + + expect(address_resolution_policy).to receive(:resolve).with(additional_rpc_addresses[0]).and_return(additional_rpc_addresses[0]) + expect(address_resolution_policy).to receive(:resolve).with(additional_rpc_addresses[1]).and_return(additional_rpc_addresses[1]) + + # RUBY-255: We should never try to do address resolution of nil. + expect(address_resolution_policy).to_not receive(:resolve).with(nil) + + # We should give up on a peer if its rpc_address is the local host. This is indicated by *not* doing + # an address resolution of it. + expect(address_resolution_policy).to_not receive(:resolve).with('127.0.0.9') + expect(address_resolution_policy).to_not receive(:resolve).with('127.0.0.1') + + handle_request do |request| + case request + when Protocol::QueryRequest + case request.cql + when /FROM system\.peers/ + rows = min_peers[0].times.map do + ip = additional_rpc_addresses.shift + { + 'peer' => ip, + 'rack' => racks[ip], + 'data_center' => data_centers[ip], + 'host_id' => host_ids[ip], + 'rpc_address' => bind_all_rpc_addresses ? IPAddr.new('0.0.0.0') : ip, + 'release_version' => release_versions[ip] + } + end + + rows << { + 'peer' => '127.0.0.9', + 'rack' => nil, + 'data_center' => nil, + 'host_id' => nil, + 'rpc_address' => connections.first.host, + 'release_version' => nil + } + Protocol::RowsResultResponse.new(nil, nil, rows, peer_metadata, nil, nil) + end + end + end + + control_connection.connect_async.value + + expect(cluster_registry).to have(3).hosts + end + end + context 'when the nodes have 0.0.0.0 as rpc_address' do let :bind_all_rpc_addresses do true From e30ca0ae37d1ab9a55fa25032fa98cbc798c76b0 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Tue, 30 Aug 2016 10:34:10 -0700 Subject: [PATCH 117/196] RUBY-255 * Add more constraints to peer record validity check, for 3.1. --- lib/cassandra/cluster/control_connection.rb | 2 +- .../cluster/control_connection_spec.rb | 284 ++++++++++++++++-- 2 files changed, 256 insertions(+), 30 deletions(-) diff --git a/lib/cassandra/cluster/control_connection.rb b/lib/cassandra/cluster/control_connection.rb index 73b0d37ab..022134a48 100644 --- a/lib/cassandra/cluster/control_connection.rb +++ b/lib/cassandra/cluster/control_connection.rb @@ -668,7 +668,7 @@ def connect_to_host(host) def peer_ip(data, host_address) peer = data['peer'] - return nil unless peer + return nil unless peer && data['host_id'] && data['data_center'] && data['rack'] && data['tokens'] rpc_address = data['rpc_address'] diff --git a/spec/cassandra/cluster/control_connection_spec.rb b/spec/cassandra/cluster/control_connection_spec.rb index 3ea47a7fe..fddf3af2f 100644 --- a/spec/cassandra/cluster/control_connection_spec.rb +++ b/spec/cassandra/cluster/control_connection_spec.rb @@ -114,6 +114,10 @@ def handle_request(&handler) ::Hash.new('rack1') end + let :tokens do + Hash.new('token1') + end + let :release_versions do ::Hash.new('2.0.7-SNAPSHOT') end @@ -178,7 +182,8 @@ def handle_request(&handler) 'rack' => racks[ip], 'data_center' => data_centers[ip], 'host_id' => host_ids[ip], - 'release_version' => release_versions[ip] + 'release_version' => release_versions[ip], + 'tokens' => tokens[ip] } ] Protocol::RowsResultResponse.new(nil, nil, rows, peer_metadata, nil, nil) @@ -191,7 +196,8 @@ def handle_request(&handler) 'data_center' => data_centers[ip], 'host_id' => host_ids[ip], 'rpc_address' => bind_all_rpc_addresses ? ::IPAddr.new('0.0.0.0') : ip, - 'release_version' => release_versions[ip] + 'release_version' => release_versions[ip], + 'tokens' => tokens[ip] } end Protocol::RowsResultResponse.new(nil, nil, rows, peer_metadata, nil, nil) @@ -348,17 +354,19 @@ def handle_request(&handler) 'data_center' => data_centers[ip], 'host_id' => host_ids[ip], 'rpc_address' => bind_all_rpc_addresses ? ::IPAddr.new('0.0.0.0') : ip, - 'release_version' => release_versions[ip] + 'release_version' => release_versions[ip], + 'tokens' => tokens[ip] } end rows << { - 'peer' => nil, - 'rack' => nil, - 'data_center' => nil, - 'host_id' => nil, - 'rpc_address' => '127.1.2.3', - 'release_version' => nil + 'peer' => nil, + 'rack' => racks['127.1.2.3'], + 'data_center' => data_centers['127.1.2.3'], + 'host_id' => host_ids['127.1.2.3'], + 'rpc_address' => '127.1.2.3', + 'release_version' => release_versions['127.1.2.3'], + 'tokens' => tokens['127.1.2.3'] } Protocol::RowsResultResponse.new(nil, nil, rows, peer_metadata, nil, nil) end @@ -371,8 +379,8 @@ def handle_request(&handler) end end - context 'with empty rpc_address' do - it 'skips empty rpc_address' do + context 'with empty rack' do + it 'skips empty rack' do additional_rpc_addresses = additional_nodes.dup expect(address_resolution_policy).to receive(:resolve).with(additional_rpc_addresses[0]).and_return(additional_rpc_addresses[0]) @@ -397,18 +405,228 @@ def handle_request(&handler) 'rack' => racks[ip], 'data_center' => data_centers[ip], 'host_id' => host_ids[ip], - 'rpc_address' => bind_all_rpc_addresses ? ::IPAddr.new('0.0.0.0') : ip, - 'release_version' => release_versions[ip] + 'rpc_address' => bind_all_rpc_addresses ? IPAddr.new('0.0.0.0') : ip, + 'release_version' => release_versions[ip], + 'tokens' => tokens[ip] } end rows << { 'peer' => '127.1.2.3', 'rack' => nil, + 'data_center' => data_centers['127.1.2.3'], + 'host_id' => host_ids['127.1.2.3'], + 'rpc_address' => '127.1.2.3', + 'release_version' => release_versions['127.1.2.3'], + 'tokens' => tokens['127.1.2.3'] + } + Protocol::RowsResultResponse.new(nil, nil, rows, peer_metadata, nil, nil) + end + end + end + + control_connection.connect_async.value + + expect(cluster_registry).to have(3).hosts + end + end + + context 'with empty data_center' do + it 'skips empty data_center' do + additional_rpc_addresses = additional_nodes.dup + + expect(address_resolution_policy).to receive(:resolve).with(additional_rpc_addresses[0]).and_return(additional_rpc_addresses[0]) + expect(address_resolution_policy).to receive(:resolve).with(additional_rpc_addresses[1]).and_return(additional_rpc_addresses[1]) + + # RUBY-255: We should never try to do address resolution of nil. + expect(address_resolution_policy).to_not receive(:resolve).with(nil) + + # We should give up on a peer if 'rpc-address' is empty. This is indicated by *not* doing + # an address resolution of peer. + expect(address_resolution_policy).to_not receive(:resolve).with('127.1.2.3') + + handle_request do |request| + case request + when Protocol::QueryRequest + case request.cql + when /FROM system\.peers/ + rows = min_peers[0].times.map do + ip = additional_rpc_addresses.shift + { + 'peer' => ip, + 'rack' => racks[ip], + 'data_center' => data_centers[ip], + 'host_id' => host_ids[ip], + 'rpc_address' => bind_all_rpc_addresses ? IPAddr.new('0.0.0.0') : ip, + 'release_version' => release_versions[ip], + 'tokens' => tokens[ip] + } + end + + rows << { + 'peer' => '127.1.2.3', + 'rack' => racks['127.1.2.3'], 'data_center' => nil, + 'host_id' => host_ids['127.1.2.3'], + 'rpc_address' => '127.1.2.3', + 'release_version' => release_versions['127.1.2.3'], + 'tokens' => tokens['127.1.2.3'] + } + Protocol::RowsResultResponse.new(nil, nil, rows, peer_metadata, nil, nil) + end + end + end + + control_connection.connect_async.value + + expect(cluster_registry).to have(3).hosts + end + end + + context 'with empty host_id' do + it 'skips empty host_id' do + additional_rpc_addresses = additional_nodes.dup + + expect(address_resolution_policy).to receive(:resolve).with(additional_rpc_addresses[0]).and_return(additional_rpc_addresses[0]) + expect(address_resolution_policy).to receive(:resolve).with(additional_rpc_addresses[1]).and_return(additional_rpc_addresses[1]) + + # RUBY-255: We should never try to do address resolution of nil. + expect(address_resolution_policy).to_not receive(:resolve).with(nil) + + # We should give up on a peer if 'rpc-address' is empty. This is indicated by *not* doing + # an address resolution of peer. + expect(address_resolution_policy).to_not receive(:resolve).with('127.1.2.3') + + handle_request do |request| + case request + when Protocol::QueryRequest + case request.cql + when /FROM system\.peers/ + rows = min_peers[0].times.map do + ip = additional_rpc_addresses.shift + { + 'peer' => ip, + 'rack' => racks[ip], + 'data_center' => data_centers[ip], + 'host_id' => host_ids[ip], + 'rpc_address' => bind_all_rpc_addresses ? IPAddr.new('0.0.0.0') : ip, + 'release_version' => release_versions[ip], + 'tokens' => tokens[ip] + } + end + + rows << { + 'peer' => '127.1.2.3', + 'rack' => racks['127.1.2.3'], + 'data_center' => data_centers['127.1.2.3'], 'host_id' => nil, + 'rpc_address' => '127.1.2.3', + 'release_version' => release_versions['127.1.2.3'], + 'tokens' => tokens['127.1.2.3'] + } + Protocol::RowsResultResponse.new(nil, nil, rows, peer_metadata, nil, nil) + end + end + end + + control_connection.connect_async.value + + expect(cluster_registry).to have(3).hosts + end + end + + context 'with empty rpc_address' do + it 'skips empty rpc_address' do + additional_rpc_addresses = additional_nodes.dup + + expect(address_resolution_policy).to receive(:resolve).with(additional_rpc_addresses[0]).and_return(additional_rpc_addresses[0]) + expect(address_resolution_policy).to receive(:resolve).with(additional_rpc_addresses[1]).and_return(additional_rpc_addresses[1]) + + # RUBY-255: We should never try to do address resolution of nil. + expect(address_resolution_policy).to_not receive(:resolve).with(nil) + + # We should give up on a peer if 'rpc-address' is empty. This is indicated by *not* doing + # an address resolution of peer. + expect(address_resolution_policy).to_not receive(:resolve).with('127.1.2.3') + + handle_request do |request| + case request + when Protocol::QueryRequest + case request.cql + when /FROM system\.peers/ + rows = min_peers[0].times.map do + ip = additional_rpc_addresses.shift + { + 'peer' => ip, + 'rack' => racks[ip], + 'data_center' => data_centers[ip], + 'host_id' => host_ids[ip], + 'rpc_address' => bind_all_rpc_addresses ? ::IPAddr.new('0.0.0.0') : ip, + 'release_version' => release_versions[ip], + 'tokens' => tokens[ip] + } + end + + rows << { + 'peer' => '127.1.2.3', + 'rack' => racks['127.1.2.3'], + 'data_center' => data_centers['127.1.2.3'], + 'host_id' => host_ids['127.1.2.3'], 'rpc_address' => nil, - 'release_version' => nil + 'release_version' => release_versions['127.1.2.3'], + 'tokens' => tokens['127.1.2.3'] + } + Protocol::RowsResultResponse.new(nil, nil, rows, peer_metadata, nil, nil) + end + end + end + + control_connection.connect_async.value + + expect(cluster_registry).to have(3).hosts + end + end + + context 'with empty tokens' do + it 'skips empty tokens' do + additional_rpc_addresses = additional_nodes.dup + + expect(address_resolution_policy).to receive(:resolve).with(additional_rpc_addresses[0]).and_return(additional_rpc_addresses[0]) + expect(address_resolution_policy).to receive(:resolve).with(additional_rpc_addresses[1]).and_return(additional_rpc_addresses[1]) + + # RUBY-255: We should never try to do address resolution of nil. + expect(address_resolution_policy).to_not receive(:resolve).with(nil) + + # We should give up on a peer if 'rpc-address' is empty. This is indicated by *not* doing + # an address resolution of peer. + expect(address_resolution_policy).to_not receive(:resolve).with('127.1.2.3') + + handle_request do |request| + case request + when Protocol::QueryRequest + case request.cql + when /FROM system\.peers/ + rows = min_peers[0].times.map do + ip = additional_rpc_addresses.shift + { + 'peer' => ip, + 'rack' => racks[ip], + 'data_center' => data_centers[ip], + 'host_id' => host_ids[ip], + 'rpc_address' => bind_all_rpc_addresses ? ::IPAddr.new('0.0.0.0') : ip, + 'release_version' => release_versions[ip], + 'tokens' => tokens[ip] + } + end + + rows << { + 'peer' => '127.1.2.3', + 'rack' => racks['127.1.2.3'], + 'data_center' => data_centers['127.1.2.3'], + 'host_id' => host_ids['127.1.2.3'], + 'rpc_address' => '127.1.2.3', + 'release_version' => release_versions['127.1.2.3'], + 'tokens' => nil } Protocol::RowsResultResponse.new(nil, nil, rows, peer_metadata, nil, nil) end @@ -449,17 +667,19 @@ def handle_request(&handler) 'data_center' => data_centers[ip], 'host_id' => host_ids[ip], 'rpc_address' => bind_all_rpc_addresses ? IPAddr.new('0.0.0.0') : ip, - 'release_version' => release_versions[ip] + 'release_version' => release_versions[ip], + 'tokens' => tokens[ip] } end rows << { 'peer' => connections.first.host, - 'rack' => nil, - 'data_center' => nil, - 'host_id' => nil, - 'rpc_address' => '127.0.0.9', - 'release_version' => nil + 'rack' => racks['127.0.0.9'], + 'data_center' => data_centers['127.0.0.9'], + 'host_id' => host_ids['127.0.0.9'], + 'rpc_address' => bind_all_rpc_addresses ? IPAddr.new('0.0.0.0') : '127.0.0.9', + 'release_version' => release_versions['127.0.0.9'], + 'tokens' => tokens['127.0.0.9'] } Protocol::RowsResultResponse.new(nil, nil, rows, peer_metadata, nil, nil) end @@ -498,17 +718,19 @@ def handle_request(&handler) 'data_center' => data_centers[ip], 'host_id' => host_ids[ip], 'rpc_address' => bind_all_rpc_addresses ? IPAddr.new('0.0.0.0') : ip, - 'release_version' => release_versions[ip] + 'release_version' => release_versions[ip], + 'tokens' => tokens[ip] } end rows << { 'peer' => '127.0.0.9', - 'rack' => nil, - 'data_center' => nil, - 'host_id' => nil, + 'rack' => racks['127.0.0.9'], + 'data_center' => data_centers['127.0.0.9'], + 'host_id' => host_ids['127.0.0.9'], 'rpc_address' => connections.first.host, - 'release_version' => nil + 'release_version' => release_versions['127.0.0.9'], + 'tokens' => tokens['127.0.0.9'] } Protocol::RowsResultResponse.new(nil, nil, rows, peer_metadata, nil, nil) end @@ -645,7 +867,8 @@ def handle_request(&handler) 'rack' => racks[ip], 'data_center' => data_centers[ip], 'host_id' => host_ids[ip], - 'release_version' => release_versions[ip] + 'release_version' => release_versions[ip], + 'tokens' => tokens[ip] }) connections.first.trigger_event(event) @@ -711,7 +934,8 @@ def handle_request(&handler) 'rack' => racks[ip], 'data_center' => data_centers[ip], 'host_id' => host_ids[ip], - 'release_version' => release_versions[ip] + 'release_version' => release_versions[ip], + 'tokens' => tokens[ip] }) connections.first.trigger_event(event) end @@ -752,7 +976,8 @@ def handle_request(&handler) 'rack' => racks[ip], 'data_center' => data_centers[ip], 'host_id' => host_ids[ip], - 'release_version' => release_versions[ip] + 'release_version' => release_versions[ip], + 'tokens' => tokens[ip] } ] Protocol::RowsResultResponse.new(nil, nil, rows, peer_metadata, nil, nil) @@ -775,7 +1000,8 @@ def handle_request(&handler) 'rack' => racks[ip], 'data_center' => data_centers[ip], 'host_id' => host_ids[ip], - 'release_version' => release_versions[ip] + 'release_version' => release_versions[ip], + 'tokens' => tokens[ip] }) io_reactor.advance_time(reconnect_interval) end From 2bbd9f43cae1fb4b2470a8a3e20d92602532bff3 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Tue, 30 Aug 2016 11:00:18 -0700 Subject: [PATCH 118/196] RUBY-255 * Fix a few tests for 3.1. --- spec/cassandra/cluster/control_connection_spec.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/spec/cassandra/cluster/control_connection_spec.rb b/spec/cassandra/cluster/control_connection_spec.rb index fddf3af2f..703313f0f 100644 --- a/spec/cassandra/cluster/control_connection_spec.rb +++ b/spec/cassandra/cluster/control_connection_spec.rb @@ -115,7 +115,7 @@ def handle_request(&handler) end let :tokens do - Hash.new('token1') + Hash.new(['token1']) end let :release_versions do @@ -153,6 +153,7 @@ def handle_request(&handler) connection[:spec_data_center] = data_centers[connection.host] connection[:spec_host_id] = host_ids[connection.host] connection[:spec_release_version] = release_versions[connection.host] + connection[:spec_tokens] = tokens[connection.host] connection.handle_request do |request, timeout| additional_rpc_addresses = additional_nodes.dup @@ -172,7 +173,8 @@ def handle_request(&handler) 'rack' => connection[:spec_rack], 'data_center' => connection[:spec_data_center], 'host_id' => connection[:spec_host_id], - 'release_version' => connection[:spec_release_version] + 'release_version' => connection[:spec_release_version], + 'tokens' => connection[:spec_tokens] } Protocol::RowsResultResponse.new(nil, nil, [row], local_metadata, nil, nil) when /FROM system\.peers WHERE peer = '?(\S+)'/ @@ -324,6 +326,7 @@ def handle_request(&handler) host.rack.should == racks[ip] host.datacenter.should == data_centers[ip] host.release_version.should == release_versions[ip] + host.tokens.should == tokens[ip] end end @@ -757,6 +760,7 @@ def handle_request(&handler) host.rack.should == racks[ip] host.datacenter.should == data_centers[ip] host.release_version.should == release_versions[ip] + host.tokens.should == tokens[ip] end end end From 9c98bca5707e4e12e923491ff71969e7c0c9a9f4 Mon Sep 17 00:00:00 2001 From: Kishan Karunaratne Date: Tue, 30 Aug 2016 15:19:42 -0700 Subject: [PATCH 119/196] [RUBY-255] Tests for missing peer columns --- integration/control_connection_test.rb | 91 ++++++++++++++++++++++++++ support/ccm.rb | 24 ++++--- 2 files changed, 106 insertions(+), 9 deletions(-) create mode 100644 integration/control_connection_test.rb diff --git a/integration/control_connection_test.rb b/integration/control_connection_test.rb new file mode 100644 index 000000000..9287e829e --- /dev/null +++ b/integration/control_connection_test.rb @@ -0,0 +1,91 @@ +# encoding: utf-8 + +#-- +# Copyright 2013-2016 DataStax, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + +require File.dirname(__FILE__) + '/integration_test_case.rb' + +class ControlConnectionTest < IntegrationTestCase + + def self.before_suite + @@ccm_cluster = CCM.setup_cluster(1, 2) + end + + def self.after_suite + CCM.remove_cluster(@@ccm_cluster.name) + end + + def remove_peer_info(info) + # Make sure to only connect to node1, as each node has its own peers table + allowed_ips = ['127.0.0.1'] + round_robin = Cassandra::LoadBalancing::Policies::RoundRobin.new + whitelist = Cassandra::LoadBalancing::Policies::WhiteList.new(allowed_ips, round_robin) + cluster = Cassandra.cluster(load_balancing_policy: whitelist) + session = cluster.connect + + value = session.execute("SELECT #{info} FROM system.peers WHERE peer = '127.0.0.2'").first[info] + session.execute("DELETE #{info} FROM system.peers WHERE peer = '127.0.0.2'") + result = session.execute("SELECT #{info} FROM system.peers WHERE peer = '127.0.0.2'").first + assert_nil result[info] + + cluster.close + value + end + + def restore_peer_info(info, value) + # Make sure to only connect to node1, as each node has its own peers table + allowed_ips = ['127.0.0.1'] + round_robin = Cassandra::LoadBalancing::Policies::RoundRobin.new + whitelist = Cassandra::LoadBalancing::Policies::WhiteList.new(allowed_ips, round_robin) + cluster = Cassandra.cluster(load_balancing_policy: whitelist) + session = cluster.connect + + session.execute("UPDATE system.peers SET #{info}=? WHERE peer = '127.0.0.2'", arguments: [value]) + result = session.execute("SELECT #{info} FROM system.peers WHERE peer = '127.0.0.2'").first + assert_equal value, result[info] + + cluster.close + end + + # Test for null columns in peer + # + # test_missing_peer_columns tests that the control connection ignores any peers which have missing peer columns. + # Using a simple 2-node cluster, it first removes one of the peer columns of node2 from node1's system.peers + # table. It then uses node1 explicitly as the control connection and verifies that node2 has not been used as a host. + # It finally restores the peer column in node1 so the next test case can continue.g + # + # @since 2.1.7 + # @jira_ticket RUBY-255 + # @expected_result Node2 should not be used as a host + # + # @test_assumptions A 2-node Cassandra cluster. + # @test_category control_connection + # + def test_missing_peer_columns + peer_info = ['host_id', 'data_center', 'rack', 'rpc_address', 'tokens'] + + peer_info.each do |info| + begin + original_value = remove_peer_info(info) + cluster = Cassandra.cluster(hosts: ['127.0.0.1']) + assert_equal ['127.0.0.1'], cluster.hosts.map { |h| h.ip.to_s } + cluster.close + ensure + restore_peer_info(info, original_value) + end + end + end +end diff --git a/support/ccm.rb b/support/ccm.rb index 30ff6d880..7b6ce72f6 100644 --- a/support/ccm.rb +++ b/support/ccm.rb @@ -455,6 +455,7 @@ def start(jvm_arg=nil) options[:load_balancing_policy] = SameOrderLoadBalancingPolicy.new + total_attempts = 1 until @nodes.all?(&:up?) && @cluster && @cluster.hosts.select(&:up?).count == @nodes.size attempts = 1 @@ -493,6 +494,11 @@ def start(jvm_arg=nil) $stderr.puts "not all hosts are up yet, retrying in 1s..." sleep(1) end + + total_attempts += 1 + if total_attempts >= 20 + raise "Cluster hosts did not match node count. nodes:#{@nodes.size}, hosts:#{@cluster.hosts.select(&:up?).count}" + end end $stderr.puts "creating session" @@ -871,6 +877,15 @@ def setup_cluster(no_dc = 1, no_nodes_per_dc = 3) @current_cluster end + def remove_cluster(name) + cluster = clusters.find {|c| c.name == name} + return unless cluster + ccm.exec('remove', cluster.name) + clusters.delete(cluster) + @current_cluster = nil if @current_cluster.name == name + nil + end + private def ccm @@ -918,15 +933,6 @@ def switch_cluster(name) nil end - def remove_cluster(name) - cluster = clusters.find {|c| c.name == name} - return unless cluster - ccm.exec('remove', cluster.name) - clusters.delete(cluster) - - nil - end - def create_cluster(name, version, datacenters, nodes_per_datacenter) nodes = Array.new(datacenters, nodes_per_datacenter).join(':') From 6bc67ef6f3061095ae21a6389fcea0c8892d76a8 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Tue, 30 Aug 2016 16:35:25 -0700 Subject: [PATCH 120/196] RUBY-187 * Add access to index collection in Keyspace * Add access to materialized-views in Table * Add trigger domain object and add collection of triggers to Table. --- integration/indexes/indexes_test.rb | 2 + integration/indexes/materialized_view_test.rb | 4 + lib/cassandra.rb | 1 + lib/cassandra/index.rb | 2 +- lib/cassandra/keyspace.rb | 35 ++++ lib/cassandra/table.rb | 72 +++++++ lib/cassandra/trigger.rb | 67 +++++++ spec/cassandra/keyspace_spec.rb | 153 +++++++++++---- spec/cassandra/table_spec.rb | 175 ++++++++++++++++-- 9 files changed, 451 insertions(+), 60 deletions(-) create mode 100644 lib/cassandra/trigger.rb diff --git a/integration/indexes/indexes_test.rb b/integration/indexes/indexes_test.rb index 68135cb77..fc2e46c08 100644 --- a/integration/indexes/indexes_test.rb +++ b/integration/indexes/indexes_test.rb @@ -54,6 +54,8 @@ def test_can_create_index assert @cluster.keyspace('simplex').table('test').has_index?('b_index') index = @cluster.keyspace('simplex').table('test').index('b_index') + assert @cluster.keyspace('simplex').has_index?('b_index') + assert_same(index, @cluster.keyspace('simplex').index('b_index')) assert_equal 'b_index', index.name assert_equal 'test', index.table.name assert_equal :composites, index.kind diff --git a/integration/indexes/materialized_view_test.rb b/integration/indexes/materialized_view_test.rb index 27fcc61bf..d30b36121 100644 --- a/integration/indexes/materialized_view_test.rb +++ b/integration/indexes/materialized_view_test.rb @@ -70,6 +70,10 @@ def test_can_retrieve_materialized_view_metadata assert @cluster.keyspace('simplex').has_materialized_view?('monthlyhigh') mv_meta = @cluster.keyspace('simplex').materialized_view('monthlyhigh') + table = @cluster.keyspace('simplex').table('scores') + assert table.has_materialized_view?('monthlyhigh') + assert_same(mv_meta, table.materialized_view('monthlyhigh')) + assert_equal 'monthlyhigh', mv_meta.name refute_nil mv_meta.id assert_equal 'scores', mv_meta.base_table.name diff --git a/lib/cassandra.rb b/lib/cassandra.rb index a9e1ad9ea..868f2a206 100644 --- a/lib/cassandra.rb +++ b/lib/cassandra.rb @@ -820,6 +820,7 @@ def self.validate_and_massage_options(options) require 'cassandra/materialized_view' require 'cassandra/keyspace' require 'cassandra/index' +require 'cassandra/trigger' require 'cassandra/execution/info' require 'cassandra/execution/options' diff --git a/lib/cassandra/index.rb b/lib/cassandra/index.rb index db700e6d7..45ac0b6f3 100644 --- a/lib/cassandra/index.rb +++ b/lib/cassandra/index.rb @@ -62,7 +62,7 @@ def custom_class_name @options['class_name'] end - # @return [String] a cql representation of this table + # @return [String] a cql representation of this index def to_cql keyspace_name = Util.escape_name(@table.keyspace.name) table_name = Util.escape_name(@table.name) diff --git a/lib/cassandra/keyspace.rb b/lib/cassandra/keyspace.rb index f45a5eeeb..751746bc8 100644 --- a/lib/cassandra/keyspace.rb +++ b/lib/cassandra/keyspace.rb @@ -69,11 +69,18 @@ def initialize(name, @views = views # Set the keyspace attribute on the tables and views. + # Also set up the index collection on the keyspace and the view-collection on each table. + @indexes_hash = {} @tables.each_value do |t| t.set_keyspace(self) + t.each_index do |index| + @indexes_hash[index.name] = index + end end @views.each_value do |v| v.set_keyspace(self) + table = v.base_table + table.add_view(v) if table end end @@ -110,6 +117,34 @@ def each_table(&block) end alias tables each_table + # @return [Boolean] whether this keyspace has an index with the given name + # @param name [String] index name + def has_index?(name) + @indexes_hash.key?(name) + end + + # @return [Cassandra::Index, nil] an index or nil + # @param name [String] index name + def index(name) + @indexes_hash[name] + end + + # Yield or enumerate each index defined in this keyspace + # @overload each_index + # @yieldparam index [Cassandra::Index] current index + # @return [Cassandra::Keyspace] self + # @overload each_index + # @return [Array] a list of indexes + def each_index(&block) + if block_given? + @indexes_hash.each_value(&block) + self + else + @indexes_hash.values + end + end + alias indexes each_index + # @return [Boolean] whether this keyspace has a materialized view with the given name # @param name [String] materialized view name def has_materialized_view?(name) diff --git a/lib/cassandra/table.rb b/lib/cassandra/table.rb index 8f9f5e592..9ed97fb2f 100644 --- a/lib/cassandra/table.rb +++ b/lib/cassandra/table.rb @@ -38,6 +38,10 @@ def initialize(keyspace, @clustering_order = clustering_order.freeze @indexes = [] @indexes_hash = {} + @materialized_views = [] + @materialized_views_hash = {} + @triggers = [] + @triggers_hash = {} end # @param name [String] index name @@ -68,6 +72,62 @@ def each_index(&block) end alias indexes each_index + # @param name [String] trigger name + # @return [Boolean] whether this table has a given trigger + def has_trigger?(name) + @triggers_hash.key?(name) + end + + # @param name [String] trigger name + # @return [Cassandra::Trigger, nil] a trigger or nil + def trigger(name) + @triggers_hash[name] + end + + # Yield or enumerate each trigger bound to this table + # @overload each_trigger + # @yieldparam trigger [Cassandra::Index] current trigger + # @return [Cassandra::Table] self + # @overload each_trigger + # @return [Array] a list of triggers + def each_trigger(&block) + if block_given? + @triggers.each(&block) + self + else + @triggers.freeze + end + end + alias triggers each_trigger + + # @param name [String] materialized view name + # @return [Boolean] whether this table has a given materialized view + def has_materialized_view?(name) + @materialized_views_hash.key?(name) + end + + # @param name [String] materialized view name + # @return [Cassandra::MaterializedView, nil] a materialized view or nil + def materialized_view(name) + @materialized_views_hash[name] + end + + # Yield or enumerate each materialized view bound to this table + # @overload each_materialized_view + # @yieldparam materialized_view [Cassandra::MaterializedView] current materialized view + # @return [Cassandra::Table] self + # @overload each_materialized_view + # @return [Array] a list of materialized views + def each_materialized_view(&block) + if block_given? + @materialized_views.each(&block) + self + else + @materialized_views.freeze + end + end + alias materialized_views each_materialized_view + # @return [String] a cql representation of this table def to_cql cql = "CREATE TABLE #{Util.escape_name(@keyspace.name)}.#{Util.escape_name(@name)} (\n" @@ -134,6 +194,18 @@ def add_index(index) @indexes_hash[index.name] = index end + # @private + def add_view(view) + @materialized_views << view + @materialized_views_hash[view.name] = view + end + + # @private + def add_trigger(trigger) + @triggers << trigger + @triggers_hash[trigger.name] = trigger + end + # @private def eql?(other) other.is_a?(Table) && diff --git a/lib/cassandra/trigger.rb b/lib/cassandra/trigger.rb new file mode 100644 index 000000000..8ff413d9c --- /dev/null +++ b/lib/cassandra/trigger.rb @@ -0,0 +1,67 @@ +# encoding: utf-8 + +#-- +# Copyright 2013-2016 DataStax, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + +module Cassandra + # Represents a trigger on a cassandra table + class Trigger + # @return [Cassandra::Table] table that the trigger applies to. + attr_reader :table + # @return [String] name of the trigger. + attr_reader :name + # @return [Hash] options of the trigger. + attr_reader :options + + # @private + def initialize(table, + name, + options) + @table = table + @name = name.freeze + @options = options.freeze + end + + # @return [String] name of the trigger class + def custom_class_name + @options['class_name'] + end + + # @return [String] a cql representation of this trigger + def to_cql + keyspace_name = Util.escape_name(@table.keyspace.name) + table_name = Util.escape_name(@table.name) + trigger_name = Util.escape_name(@name) + + "CREATE TRIGGER #{trigger_name} ON #{keyspace_name}.#{table_name} USING '#{@options['class_name']}';" + end + + # @private + def eql?(other) + other.is_a?(Trigger) && + @table == other.table && + @name == other.name && + @options == other.options + end + alias == eql? + + # @private + def inspect + "#<#{self.class.name}:0x#{object_id.to_s(16)} " \ + "@name=#{@name.inspect} @table=#{@table.inspect} @options=#{@options.inspect}>" + end + end +end diff --git a/spec/cassandra/keyspace_spec.rb b/spec/cassandra/keyspace_spec.rb index 360446544..9509fb9c4 100644 --- a/spec/cassandra/keyspace_spec.rb +++ b/spec/cassandra/keyspace_spec.rb @@ -22,69 +22,140 @@ module Cassandra describe(Keyspace) do let(:view) { double('view') } + let(:view2) { double('view2') } let(:table) { double('table') } - let(:ks) { Keyspace.new('myks', true, nil, {'mytable' => table}, nil, nil, nil, {'myview' => view}) } + let(:table2) { double('table2') } + let(:index1) { double('index') } + let(:index2) { double('index2') } - before do - allow(view).to receive(:set_keyspace) - allow(table).to receive(:set_keyspace) - end + context :view do + let(:ks) { Keyspace.new('myks', true, nil, { 'mytable' => table, 'tbl2' => table2 }, + nil, nil, nil, { 'myview' => view, 'view2' => view2 }) + } - context :has_materialized_view? do - it 'should return true if the view exists and has a base-table' do - expect(view).to receive(:base_table).and_return(:table) - expect(ks.has_materialized_view?('myview')).to be_truthy + before do + allow(view).to receive(:set_keyspace) + allow(view2).to receive(:set_keyspace) + allow(view2).to receive(:base_table) + allow(table).to receive(:set_keyspace) + allow(table2).to receive(:set_keyspace) + allow(table).to receive(:each_index) + allow(table2).to receive(:each_index) end - it 'should return false if the view exists but does not have a base-table' do - expect(view).to receive(:base_table).and_return(nil) - expect(ks.has_materialized_view?('myview')).to be_falsey + context :has_materialized_view? do + it 'should return true if the view exists and has a base-table' do + expect(view).to receive(:base_table).and_return(table).exactly(2).times + expect(table).to receive(:add_view).with(view) + expect(ks.has_materialized_view?('myview')).to be_truthy + end + + it 'should return false if the view exists but does not have a base-table' do + expect(view).to receive(:base_table).and_return(nil).exactly(2).times + expect(ks.has_materialized_view?('myview')).to be_falsey + end end - end - context :materialized_view do - it 'should return the view if it exists and has a base-table' do - expect(view).to receive(:base_table).and_return(:table) - expect(ks.materialized_view('myview')).to be(view) + context :materialized_view do + it 'should return the view if it exists and has a base-table' do + expect(view).to receive(:base_table).and_return(table).exactly(2).times + expect(table).to receive(:add_view).with(view) + expect(ks.materialized_view('myview')).to be(view) + end + + it 'should return nil if the view exists but does not have a base-table' do + expect(view).to receive(:base_table).and_return(nil).exactly(2).times + expect(ks.materialized_view('myview')).to be_nil + end end - it 'should return nil if the view exists but does not have a base-table' do - expect(view).to receive(:base_table).and_return(nil) - expect(ks.materialized_view('myview')).to be_nil + context :materialized_views do + it 'should return the view if it exists and has a base-table' do + expect(view).to receive(:base_table).and_return(table).exactly(2).times + expect(table).to receive(:add_view).with(view) + expect(ks.materialized_views).to eq([view]) + end + + it 'should not return view if the view exists but does not have a base-table' do + expect(view).to receive(:base_table).and_return(nil).exactly(2).times + expect(ks.materialized_views).to be_empty + end + end + + context :each_materialized_view do + it 'should return the view if it exists and has a base-table' do + expect(view).to receive(:base_table).and_return(table).exactly(2).times + expect(table).to receive(:add_view).with(view) + result = [] + ks.each_materialized_view do |v| + result << v + end + + expect(result).to eq([view]) + end + + it 'should not return view if the view exists but does not have a base-table' do + expect(view).to receive(:base_table).and_return(nil).exactly(2).times + result = [] + ks.each_materialized_view do |v| + result << v + end + + expect(result).to be_empty + end end end - context :materialized_views do - it 'should return the view if it exists and has a base-table' do - expect(view).to receive(:base_table).and_return(:table) - expect(ks.materialized_views).to eq([view]) + context :index do + let(:ks) { Keyspace.new('myks', true, nil, { 'mytable' => table, 'tbl2' => table2 }, nil, nil, nil, {}) } + + before do + allow(table).to receive(:set_keyspace) + allow(table2).to receive(:set_keyspace) + allow(table2).to receive(:each_index) do |&block| + block.call(index1) + block.call(index2) + end + allow(table).to receive(:each_index) + allow(index1).to receive(:name).and_return("index1") + allow(index2).to receive(:name).and_return("index2") end - it 'should not return view if the view exists but does not have a base-table' do - expect(view).to receive(:base_table).and_return(nil) - expect(ks.materialized_views).to be_empty + context :has_index? do + it 'should return true if the index exists' do + expect(ks.has_index?('index1')).to be_truthy + end + + it 'should return false if the index does not exist' do + expect(ks.has_index?('myindex')).to be_falsey + end end - end - context :each_materialized_view do - it 'should return the view if it exists and has a base-table' do - expect(view).to receive(:base_table).and_return(:table) - result = [] - ks.each_materialized_view do |v| - result << v + context :index do + it 'should return the index if it exists' do + expect(ks.index('index1')).to be(index1) end - expect(result).to eq([view]) + it 'should return nil if the index does not exist' do + expect(ks.index('myindex')).to be_nil + end end - it 'should not return view if the view exists but does not have a base-table' do - expect(view).to receive(:base_table).and_return(nil) - result = [] - ks.each_materialized_view do |v| - result << v + context :indexes do + it 'should return the indexes' do + expect(ks.indexes).to eq([index1, index2]) end + end + + context :each_index do + it 'should iterate the indexes' do + result = [] + ks.each_index do |v| + result << v + end - expect(result).to be_empty + expect(result).to eq([index1, index2]) + end end end end diff --git a/spec/cassandra/table_spec.rb b/spec/cassandra/table_spec.rb index 6b7d69fc9..60180f587 100644 --- a/spec/cassandra/table_spec.rb +++ b/spec/cassandra/table_spec.rb @@ -22,27 +22,166 @@ module Cassandra describe(Table) do - context :to_cql do - let(:ks) { double('keyspace')} - let(:col) { double('col')} - let(:col2) { double('col2')} - let(:col3) { double('from')} - let(:quote_column) { double('"my"col"')} - let(:options) { double('options')} - let(:id) { 1234 } + let(:ks) { double('keyspace') } + let(:col) { double('col') } + let(:col2) { double('col2') } + let(:col3) { double('from') } + let(:quote_column) { double('"my"col"') } + let(:options) { double('options') } + let(:id) { 1234 } + before do + allow(ks).to receive(:name).and_return('myks1') + allow(col).to receive(:name).and_return('col') + allow(col).to receive(:type).and_return(Cassandra::Types.int) + allow(col2).to receive(:name).and_return('col2') + allow(col2).to receive(:type).and_return(int) + allow(col3).to receive(:name).and_return('from') + allow(col3).to receive(:type).and_return(varchar) + allow(quote_column).to receive(:name).and_return('"my"col"') + allow(quote_column).to receive(:type).and_return(varchar) + allow(options).to receive(:to_cql).and_return('opt1=value1') + end + + context :index do + let(:index1) { double('index1') } + let(:index2) { double('index2') } + let(:table) { Table.new(ks, 'mytable1', [col], [], [col2, col3, quote_column], options, [], id) } + + before do + allow(index1).to receive(:name).and_return('index1') + allow(index2).to receive(:name).and_return('index2') + table.add_index(index1) + table.add_index(index2) + end + + context :has_index? do + it 'should find an index that exists' do + expect(table.has_index?('index1')).to be_truthy + end + + it 'should fail to find an index that does not exist' do + expect(table.has_index?('index3')).to be_falsey + end + end + + context :index do + it 'should find an index that exists' do + expect(table.index('index1')).to be(index1) + end + + it 'should fail to find an index that does not exist' do + expect(table.index('index3')).to be_nil + end + end + + it 'should return collection of indexes' do + expect(table.indexes).to eq([index1, index2]) + end + + it 'should support iterating indexes' do + result = [] + table.each_index do |index| + result << index + end + + expect(result).to eq([index1, index2]) + end + end + + context :materialized_view do + let(:view1) { double('view1') } + let(:view2) { double('view2') } + let(:table) { Table.new(ks, 'mytable1', [col], [], [col2, col3, quote_column], options, [], id) } + + before do + allow(view1).to receive(:name).and_return('view1') + allow(view2).to receive(:name).and_return('view2') + table.add_view(view1) + table.add_view(view2) + end + + context :has_materialized_view? do + it 'should find an materialized_view that exists' do + expect(table.has_materialized_view?('view1')).to be_truthy + end + + it 'should fail to find an materialized_view that does not exist' do + expect(table.has_materialized_view?('view3')).to be_falsey + end + end + + context :materialized_view do + it 'should find an materialized_view that exists' do + expect(table.materialized_view('view1')).to be(view1) + end + + it 'should fail to find an materialized_view that does not exist' do + expect(table.materialized_view('view3')).to be_nil + end + end + + it 'should return collection of materialized_views' do + expect(table.materialized_views).to eq([view1, view2]) + end + + it 'should support iterating materialized_views' do + result = [] + table.each_materialized_view do |materialized_view| + result << materialized_view + end + + expect(result).to eq([view1, view2]) + end + end + + context :trigger do + let(:trigger1) { double('trigger1') } + let(:trigger2) { double('trigger2') } + let(:table) { Table.new(ks, 'mytable1', [col], [], [col2, col3, quote_column], options, [], id) } + before do - allow(ks).to receive(:name).and_return('myks1') - allow(col).to receive(:name).and_return('col') - allow(col).to receive(:type).and_return(Cassandra::Types.int) - allow(col2).to receive(:name).and_return('col2') - allow(col2).to receive(:type).and_return(int) - allow(col3).to receive(:name).and_return('from') - allow(col3).to receive(:type).and_return(varchar) - allow(quote_column).to receive(:name).and_return('"my"col"') - allow(quote_column).to receive(:type).and_return(varchar) - allow(options).to receive(:to_cql).and_return('opt1=value1') + allow(trigger1).to receive(:name).and_return('trigger1') + allow(trigger2).to receive(:name).and_return('trigger2') + table.add_trigger(trigger1) + table.add_trigger(trigger2) end + context :has_trigger? do + it 'should find an trigger that exists' do + expect(table.has_trigger?('trigger1')).to be_truthy + end + + it 'should fail to find an trigger that does not exist' do + expect(table.has_trigger?('trigger3')).to be_falsey + end + end + + context :trigger do + it 'should find an trigger that exists' do + expect(table.trigger('trigger1')).to be(trigger1) + end + + it 'should fail to find an trigger that does not exist' do + expect(table.trigger('trigger3')).to be_nil + end + end + + it 'should return collection of triggers' do + expect(table.triggers).to eq([trigger1, trigger2]) + end + + it 'should support iterating triggers' do + result = [] + table.each_trigger do |trigger| + result << trigger + end + + expect(result).to eq([trigger1, trigger2]) + end + end + + context :to_cql do + it 'should quote keyspace, table, columns properly' do t = Table.new(ks, 'mytable1', [col], [], [col2, col3, quote_column], options, [], id) expected_cql = <<-EOF From 7e029d08ff9bb9bca332db66862eb1647a3dfc4d Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Wed, 31 Aug 2016 14:50:56 -0700 Subject: [PATCH 121/196] RUBY-187 * Add metadata fetching logic to handle trigger metadata. --- lib/cassandra/cluster/schema/fetchers.rb | 125 +++++++++++++++--- lib/cassandra/trigger.rb | 4 +- .../cluster/schema/fetchers/1.2.19-schema.cql | 2 +- .../cluster/schema/fetchers/2.0.16-data.json | 10 +- .../cluster/schema/fetchers/2.0.16-schema.cql | 4 +- .../cluster/schema/fetchers/2.1.9-data.json | 10 +- .../cluster/schema/fetchers/2.1.9-schema.cql | 4 +- .../cluster/schema/fetchers/2.2.1-data.json | 10 +- .../cluster/schema/fetchers/2.2.1-schema.cql | 4 +- .../cluster/schema/fetchers/3.0.0-data.json | 10 +- .../cluster/schema/fetchers/3.0.0-schema.cql | 4 +- .../cassandra/cluster/schema/fetchers_spec.rb | 5 + 12 files changed, 163 insertions(+), 29 deletions(-) diff --git a/lib/cassandra/cluster/schema/fetchers.rb b/lib/cassandra/cluster/schema/fetchers.rb index 4c2a46ca6..c49d60e74 100644 --- a/lib/cassandra/cluster/schema/fetchers.rb +++ b/lib/cassandra/cluster/schema/fetchers.rb @@ -34,8 +34,10 @@ def fetch(connection) select_functions(connection), select_aggregates(connection), select_materialized_views(connection), - select_indexes(connection)) - .map do |rows_keyspaces, rows_tables, rows_columns, rows_types, rows_functions, rows_aggregates, rows_views, rows_indexes| + select_indexes(connection), + select_triggers(connection)) + .map do |rows_keyspaces, rows_tables, rows_columns, rows_types, rows_functions, rows_aggregates, + rows_views, rows_indexes, rows_triggers| lookup_tables = map_rows_by(rows_tables, 'keyspace_name') lookup_columns = map_rows_by(rows_columns, 'keyspace_name') lookup_types = map_rows_by(rows_types, 'keyspace_name') @@ -43,6 +45,7 @@ def fetch(connection) lookup_aggregates = map_rows_by(rows_aggregates, 'keyspace_name') lookup_views = map_rows_by(rows_views, 'keyspace_name') lookup_indexes = map_rows_by(rows_indexes, 'keyspace_name') + lookup_triggers = map_rows_by(rows_triggers, 'keyspace_name') rows_keyspaces.map do |keyspace_data| name = keyspace_data['keyspace_name'] @@ -54,7 +57,8 @@ def fetch(connection) lookup_functions[name], lookup_aggregates[name], lookup_views[name], - lookup_indexes[name]) + lookup_indexes[name], + lookup_triggers[name]) end end end @@ -67,8 +71,9 @@ def fetch_keyspace(connection, keyspace_name) select_keyspace_functions(connection, keyspace_name), select_keyspace_aggregates(connection, keyspace_name), select_keyspace_materialized_views(connection, keyspace_name), - select_keyspace_indexes(connection, keyspace_name)) - .map do |rows_keyspaces, rows_tables, rows_columns, rows_types, rows_functions, rows_aggregates, rows_views, rows_indexes| + select_keyspace_indexes(connection, keyspace_name), + select_keyspace_triggers(connection, keyspace_name)) + .map do |rows_keyspaces, rows_tables, rows_columns, rows_types, rows_functions, rows_aggregates, rows_views, rows_indexes, rows_triggers| if rows_keyspaces.empty? nil else @@ -79,7 +84,8 @@ def fetch_keyspace(connection, keyspace_name) rows_functions, rows_aggregates, rows_views, - rows_indexes) + rows_indexes, + rows_triggers) end end end @@ -87,14 +93,16 @@ def fetch_keyspace(connection, keyspace_name) def fetch_table(connection, keyspace_name, table_name) Ione::Future.all(select_table(connection, keyspace_name, table_name), select_table_columns(connection, keyspace_name, table_name), - select_table_indexes(connection, keyspace_name, table_name)) - .map do |(rows_tables, rows_columns, rows_indexes)| + select_table_indexes(connection, keyspace_name, table_name), + select_table_triggers(connection, keyspace_name, table_name)) + .map do |(rows_tables, rows_columns, rows_indexes, rows_triggers)| if rows_tables.empty? nil else create_table(rows_tables.first, rows_columns, - rows_indexes) + rows_indexes, + rows_triggers) end end end @@ -168,6 +176,10 @@ def select_indexes(connection) FUTURE_EMPTY_LIST end + def select_triggers(connection) + FUTURE_EMPTY_LIST + end + def select_types(connection) FUTURE_EMPTY_LIST end @@ -200,6 +212,10 @@ def select_keyspace_indexes(connection, keyspace_name) FUTURE_EMPTY_LIST end + def select_keyspace_triggers(connection, keyspace_name) + FUTURE_EMPTY_LIST + end + def select_keyspace_types(connection, keyspace_name) FUTURE_EMPTY_LIST end @@ -228,6 +244,10 @@ def select_table_indexes(connection, keyspace_name, table_name) FUTURE_EMPTY_LIST end + def select_table_triggers(connection, keyspace_name, table_name) + FUTURE_EMPTY_LIST + end + def select_type(connection, keyspace_name, type_name) FUTURE_EMPTY_LIST end @@ -348,7 +368,7 @@ def create_replication(keyspace_data) def create_keyspace(keyspace_data, rows_tables, rows_columns, rows_types, rows_functions, rows_aggregates, - rows_views, rows_indexes) + rows_views, rows_indexes, rows_triggers) keyspace_name = keyspace_data['keyspace_name'] replication = create_replication(keyspace_data) types = rows_types.each_with_object({}) do |row, h| @@ -367,10 +387,14 @@ def create_keyspace(keyspace_data, rows_tables, rows_columns, end lookup_columns = map_rows_by(rows_columns, 'columnfamily_name') + lookup_indexes = map_rows_by(rows_indexes, 'columnfamily_name') + lookup_triggers = map_rows_by(rows_triggers, 'columnfamily_name') tables = rows_tables.each_with_object({}) do |row, h| table_name = row['columnfamily_name'] - # rows_indexes is nil for C* < 3.0. - h[table_name] = create_table(row, lookup_columns[table_name], nil) + h[table_name] = create_table(row, + lookup_columns[table_name], + lookup_indexes[table_name], + lookup_triggers[table_name]) end Keyspace.new(keyspace_name, @@ -383,7 +407,7 @@ def create_keyspace(keyspace_data, rows_tables, rows_columns, {}) end - def create_table(table_data, rows_columns, rows_indexes) + def create_table(table_data, rows_columns, rows_indexes, rows_triggers) keyspace_name = table_data['keyspace_name'] table_name = table_data['columnfamily_name'] key_validator = @type_parser.parse(table_data['key_validator']) @@ -461,7 +485,7 @@ def create_table(table_data, rows_columns, rows_indexes) column = create_column(row) other_columns << column - # In C* 1.2.x, index info is in the column metadata; rows_indexes is nil. + # In C* 1.2.x, index info is in the column metadata; rows_indexes is []. index_rows << [column, row] unless row['index_type'].nil? end @@ -554,12 +578,17 @@ def create_table_options(table_data, compaction_strategy, is_compact) end class V2_0_x < V1_2_x + SELECT_TRIGGERS = 'SELECT * FROM system.schema_triggers'.freeze + SELECT_KEYSPACE = 'SELECT * FROM system.schema_keyspaces WHERE keyspace_name = ?'.freeze SELECT_KEYSPACE_TABLES = 'SELECT * FROM system.schema_columnfamilies WHERE keyspace_name = ?'.freeze SELECT_KEYSPACE_COLUMNS = 'SELECT * FROM system.schema_columns WHERE keyspace_name = ?'.freeze + SELECT_KEYSPACE_TRIGGERS = + 'SELECT * FROM system.schema_triggers WHERE keyspace_name = ?'.freeze + SELECT_TABLE = 'SELECT * ' \ 'FROM system.schema_columnfamilies ' \ @@ -568,10 +597,14 @@ class V2_0_x < V1_2_x 'SELECT * ' \ 'FROM system.schema_columns ' \ 'WHERE keyspace_name = ? AND columnfamily_name = ?'.freeze + SELECT_TABLE_TRIGGERS = + 'SELECT * ' \ + 'FROM system.schema_triggers ' \ + 'WHERE keyspace_name = ? AND columnfamily_name = ?'.freeze private - def create_table(table_data, rows_columns, rows_indexes) + def create_table(table_data, rows_columns, rows_indexes, rows_triggers) keyspace_name = table_data['keyspace_name'] table_name = table_data['columnfamily_name'] comparator = @type_parser.parse(table_data['comparator']) @@ -626,9 +659,21 @@ def create_table(table_data, rows_columns, rows_indexes) index_rows.each do |column, row| create_index(table, column, row) end + + # Create Trigger objects and add them to the table. + rows_triggers.each do |row_trigger| + table.add_trigger(Cassandra::Trigger.new(table, + row_trigger['trigger_name'], + row_trigger['trigger_options'])) + end + table end + def select_triggers(connection) + send_select_request(connection, SELECT_TRIGGERS) + end + def select_keyspace(connection, keyspace_name) params = [keyspace_name] hints = [Types.varchar] @@ -647,6 +692,12 @@ def select_keyspace_columns(connection, keyspace_name) send_select_request(connection, SELECT_KEYSPACE_COLUMNS, params, hints) end + def select_keyspace_triggers(connection, keyspace_name) + params = [keyspace_name] + hints = [Types.varchar] + send_select_request(connection, SELECT_KEYSPACE_TRIGGERS, params, hints) + end + def select_table(connection, keyspace_name, table_name) params = [keyspace_name, table_name] hints = [Types.varchar, Types.varchar] @@ -659,6 +710,12 @@ def select_table_columns(connection, keyspace_name, table_name) send_select_request(connection, SELECT_TABLE_COLUMNS, params, hints) end + def select_table_triggers(connection, keyspace_name, table_name) + params = [keyspace_name, table_name] + hints = [Types.varchar, Types.varchar] + send_select_request(connection, SELECT_TABLE_TRIGGERS, params, hints) + end + def create_table_options(table_data, compaction_strategy, is_compact) compression_parameters = ::JSON.load(table_data['compression_parameters']) if compression_parameters['sstable_compression'] @@ -896,6 +953,7 @@ class V3_0_x < V2_2_x SELECT_AGGREGATES = 'SELECT * FROM system_schema.aggregates'.freeze SELECT_INDEXES = 'SELECT * FROM system_schema.indexes'.freeze SELECT_VIEWS = 'SELECT * FROM system_schema.views'.freeze + SELECT_TRIGGERS = 'SELECT * FROM system_schema.triggers'.freeze SELECT_KEYSPACE = 'SELECT * FROM system_schema.keyspaces WHERE keyspace_name = ?'.freeze @@ -913,6 +971,8 @@ class V3_0_x < V2_2_x 'SELECT * FROM system_schema.functions WHERE keyspace_name = ?'.freeze SELECT_KEYSPACE_AGGREGATES = 'SELECT * FROM system_schema.aggregates WHERE keyspace_name = ?'.freeze + SELECT_KEYSPACE_TRIGGERS = + 'SELECT * FROM system_schema.triggers WHERE keyspace_name = ?'.freeze SELECT_TABLE = 'SELECT * ' \ @@ -926,6 +986,10 @@ class V3_0_x < V2_2_x 'SELECT * ' \ 'FROM system_schema.indexes ' \ 'WHERE keyspace_name = ? AND table_name = ?'.freeze + SELECT_TABLE_TRIGGERS = + 'SELECT * ' \ + 'FROM system_schema.triggers ' \ + 'WHERE keyspace_name = ? AND table_name = ?'.freeze SELECT_VIEW = 'SELECT * ' \ @@ -984,6 +1048,10 @@ def select_columns(connection) send_select_request(connection, SELECT_COLUMNS) end + def select_triggers(connection) + send_select_request(connection, SELECT_TRIGGERS) + end + def select_types(connection) send_select_request(connection, SELECT_TYPES) end @@ -1026,6 +1094,12 @@ def select_keyspace_materialized_views(connection, keyspace_name) send_select_request(connection, SELECT_KEYSPACE_VIEWS, params, hints) end + def select_keyspace_triggers(connection, keyspace_name) + params = [keyspace_name] + hints = [Types.varchar] + send_select_request(connection, SELECT_KEYSPACE_TRIGGERS, params, hints) + end + def select_keyspace_types(connection, keyspace_name) params = [keyspace_name] hints = [Types.varchar] @@ -1071,6 +1145,12 @@ def select_materialized_view(connection, keyspace_name, view_name) send_select_request(connection, SELECT_VIEW, params, hints) end + def select_table_triggers(connection, keyspace_name, table_name) + params = [keyspace_name, table_name] + hints = [Types.varchar, Types.varchar] + send_select_request(connection, SELECT_TABLE_TRIGGERS, params, hints) + end + def select_type(connection, keyspace_name, type_name) params = [keyspace_name, type_name] hints = [Types.varchar, Types.varchar] @@ -1182,7 +1262,7 @@ def create_types(rows_types, types) end def create_keyspace(keyspace_data, rows_tables, rows_columns, rows_types, - rows_functions, rows_aggregates, rows_views, rows_indexes) + rows_functions, rows_aggregates, rows_views, rows_indexes, rows_triggers) keyspace_name = keyspace_data['keyspace_name'] replication = create_replication(keyspace_data) @@ -1206,10 +1286,11 @@ def create_keyspace(keyspace_data, rows_tables, rows_columns, rows_types, lookup_columns = map_rows_by(rows_columns, 'table_name') lookup_indexes = map_rows_by(rows_indexes, 'table_name') + lookup_triggers = map_rows_by(rows_triggers, 'table_name') tables = rows_tables.each_with_object({}) do |row, h| table_name = row['table_name'] h[table_name] = create_table(row, lookup_columns[table_name], - lookup_indexes[table_name], types) + lookup_indexes[table_name], lookup_triggers[table_name], types) end views = rows_views.each_with_object({}) do |row, h| @@ -1285,7 +1366,7 @@ def create_column(column_data, types) Column.new(name, type, order, is_static, is_frozen) end - def create_table(table_data, rows_columns, rows_indexes, types = nil) + def create_table(table_data, rows_columns, rows_indexes, rows_triggers, types = nil) keyspace_name = table_data['keyspace_name'] table_name = table_data['table_name'] table_flags = table_data['flags'] @@ -1352,6 +1433,14 @@ def create_table(table_data, rows_columns, rows_indexes, types = nil) rows_indexes.each do |row| create_index(table, row) end + + # Create Trigger objects and add them to the table. + rows_triggers.each do |row_trigger| + table.add_trigger(Cassandra::Trigger.new(table, + row_trigger['trigger_name'], + row_trigger['options'])) + end + table end diff --git a/lib/cassandra/trigger.rb b/lib/cassandra/trigger.rb index 8ff413d9c..63120a68a 100644 --- a/lib/cassandra/trigger.rb +++ b/lib/cassandra/trigger.rb @@ -37,7 +37,7 @@ def initialize(table, # @return [String] name of the trigger class def custom_class_name - @options['class_name'] + @options['class'] end # @return [String] a cql representation of this trigger @@ -46,7 +46,7 @@ def to_cql table_name = Util.escape_name(@table.name) trigger_name = Util.escape_name(@name) - "CREATE TRIGGER #{trigger_name} ON #{keyspace_name}.#{table_name} USING '#{@options['class_name']}';" + "CREATE TRIGGER #{trigger_name} ON #{keyspace_name}.#{table_name} USING '#{@options['class']}';" end # @private diff --git a/spec/cassandra/cluster/schema/fetchers/1.2.19-schema.cql b/spec/cassandra/cluster/schema/fetchers/1.2.19-schema.cql index c95c0c30c..e99cb6d61 100644 --- a/spec/cassandra/cluster/schema/fetchers/1.2.19-schema.cql +++ b/spec/cassandra/cluster/schema/fetchers/1.2.19-schema.cql @@ -392,4 +392,4 @@ WITH bloom_filter_fp_chance = 0.01 AND gc_grace_seconds = 2 AND populate_io_cache_on_flush = 'false' AND read_repair_chance = 0.1 - AND replicate_on_write = 'true'; \ No newline at end of file + AND replicate_on_write = 'true'; diff --git a/spec/cassandra/cluster/schema/fetchers/2.0.16-data.json b/spec/cassandra/cluster/schema/fetchers/2.0.16-data.json index 2905afb46..2e752af6d 100644 --- a/spec/cassandra/cluster/schema/fetchers/2.0.16-data.json +++ b/spec/cassandra/cluster/schema/fetchers/2.0.16-data.json @@ -2136,5 +2136,13 @@ "type": "regular", "validator": "org.apache.cassandra.db.marshal.Int32Type" } + ], + "SELECT * FROM system.schema_triggers": [ + { + "keyspace_name": "simplex", + "columnfamily_name": "t1", + "trigger_name": "mytrigger", + "trigger_options": { "class": "org.apache.cassandra.triggers.AuditTrigger"} + } ] -} \ No newline at end of file +} diff --git a/spec/cassandra/cluster/schema/fetchers/2.0.16-schema.cql b/spec/cassandra/cluster/schema/fetchers/2.0.16-schema.cql index 51906a71e..3bb79c272 100644 --- a/spec/cassandra/cluster/schema/fetchers/2.0.16-schema.cql +++ b/spec/cassandra/cluster/schema/fetchers/2.0.16-schema.cql @@ -469,6 +469,8 @@ WITH bloom_filter_fp_chance = 0.01 CREATE INDEX "ind1" ON simplex."t1" ("f2"); +CREATE TRIGGER mytrigger ON simplex."t1" USING 'org.apache.cassandra.triggers.AuditTrigger'; + CREATE TABLE simplex."t2" ( "f1" int, "f2" int, @@ -509,4 +511,4 @@ WITH bloom_filter_fp_chance = 0.01 AND populate_io_cache_on_flush = 'false' AND read_repair_chance = 0.1 AND replicate_on_write = 'true' - AND speculative_retry = '99.0PERCENTILE'; \ No newline at end of file + AND speculative_retry = '99.0PERCENTILE'; diff --git a/spec/cassandra/cluster/schema/fetchers/2.1.9-data.json b/spec/cassandra/cluster/schema/fetchers/2.1.9-data.json index 8d89b95f7..ec0b197d7 100644 --- a/spec/cassandra/cluster/schema/fetchers/2.1.9-data.json +++ b/spec/cassandra/cluster/schema/fetchers/2.1.9-data.json @@ -2285,5 +2285,13 @@ ], "SELECT * FROM system.schema_usertypes": [ + ], + "SELECT * FROM system.schema_triggers": [ + { + "keyspace_name": "simplex", + "columnfamily_name": "t1", + "trigger_name": "mytrigger", + "trigger_options": { "class": "org.apache.cassandra.triggers.AuditTrigger"} + } ] -} \ No newline at end of file +} diff --git a/spec/cassandra/cluster/schema/fetchers/2.1.9-schema.cql b/spec/cassandra/cluster/schema/fetchers/2.1.9-schema.cql index f596a6458..de8e0f771 100644 --- a/spec/cassandra/cluster/schema/fetchers/2.1.9-schema.cql +++ b/spec/cassandra/cluster/schema/fetchers/2.1.9-schema.cql @@ -476,6 +476,8 @@ WITH bloom_filter_fp_chance = 0.01 CREATE INDEX "ind1" ON simplex."t1" ("f2"); +CREATE TRIGGER mytrigger ON simplex."t1" USING 'org.apache.cassandra.triggers.AuditTrigger'; + CREATE TABLE simplex."t2" ( "f1" int, "f2" int, @@ -514,4 +516,4 @@ WITH bloom_filter_fp_chance = 0.01 AND memtable_flush_period_in_ms = 3600000 AND min_index_interval = 128 AND read_repair_chance = 0.1 - AND speculative_retry = '99.0PERCENTILE'; \ No newline at end of file + AND speculative_retry = '99.0PERCENTILE'; diff --git a/spec/cassandra/cluster/schema/fetchers/2.2.1-data.json b/spec/cassandra/cluster/schema/fetchers/2.2.1-data.json index 593634d04..cde2cb06b 100644 --- a/spec/cassandra/cluster/schema/fetchers/2.2.1-data.json +++ b/spec/cassandra/cluster/schema/fetchers/2.2.1-data.json @@ -3045,5 +3045,13 @@ ], "SELECT * FROM system.schema_aggregates": [ + ], + "SELECT * FROM system.schema_triggers": [ + { + "keyspace_name": "simplex", + "columnfamily_name": "t1", + "trigger_name": "mytrigger", + "trigger_options": { "class": "org.apache.cassandra.triggers.AuditTrigger"} + } ] -} \ No newline at end of file +} diff --git a/spec/cassandra/cluster/schema/fetchers/2.2.1-schema.cql b/spec/cassandra/cluster/schema/fetchers/2.2.1-schema.cql index 465e14fda..f5709d551 100644 --- a/spec/cassandra/cluster/schema/fetchers/2.2.1-schema.cql +++ b/spec/cassandra/cluster/schema/fetchers/2.2.1-schema.cql @@ -682,6 +682,8 @@ WITH bloom_filter_fp_chance = 0.01 CREATE INDEX "ind1" ON simplex."t1" ("f2"); +CREATE TRIGGER mytrigger ON simplex."t1" USING 'org.apache.cassandra.triggers.AuditTrigger'; + CREATE TABLE simplex."t2" ( "f1" int, "f2" int, @@ -720,4 +722,4 @@ WITH bloom_filter_fp_chance = 0.01 AND memtable_flush_period_in_ms = 3600000 AND min_index_interval = 128 AND read_repair_chance = 0.1 - AND speculative_retry = '99.0PERCENTILE'; \ No newline at end of file + AND speculative_retry = '99.0PERCENTILE'; diff --git a/spec/cassandra/cluster/schema/fetchers/3.0.0-data.json b/spec/cassandra/cluster/schema/fetchers/3.0.0-data.json index 78971e72c..8e1a13114 100644 --- a/spec/cassandra/cluster/schema/fetchers/3.0.0-data.json +++ b/spec/cassandra/cluster/schema/fetchers/3.0.0-data.json @@ -398,5 +398,13 @@ "speculative_retry": "99PERCENTILE", "where_clause": "f2 IS NOT NULL" } + ], + "SELECT * FROM system_schema.triggers": [ + { + "keyspace_name": "simplex", + "table_name": "t1", + "trigger_name": "mytrigger", + "options": { "class": "org.apache.cassandra.triggers.AuditTrigger"} + } ] -} \ No newline at end of file +} diff --git a/spec/cassandra/cluster/schema/fetchers/3.0.0-schema.cql b/spec/cassandra/cluster/schema/fetchers/3.0.0-schema.cql index a0f3df5cc..2e1d2f6ba 100644 --- a/spec/cassandra/cluster/schema/fetchers/3.0.0-schema.cql +++ b/spec/cassandra/cluster/schema/fetchers/3.0.0-schema.cql @@ -23,6 +23,8 @@ WITH bloom_filter_fp_chance = 0.01 CREATE INDEX "ind1" ON simplex."t1" ("f2"); +CREATE TRIGGER mytrigger ON simplex."t1" USING 'org.apache.cassandra.triggers.AuditTrigger'; + CREATE TABLE simplex."t2" ( "f1" int, "f2" int, @@ -126,4 +128,4 @@ WITH bloom_filter_fp_chance = 0.01 AND memtable_flush_period_in_ms = 42 AND min_index_interval = 128 AND read_repair_chance = 0.2 - AND speculative_retry = '99PERCENTILE'; \ No newline at end of file + AND speculative_retry = '99PERCENTILE'; diff --git a/spec/cassandra/cluster/schema/fetchers_spec.rb b/spec/cassandra/cluster/schema/fetchers_spec.rb index 9ccce29e3..28303d1dc 100644 --- a/spec/cassandra/cluster/schema/fetchers_spec.rb +++ b/spec/cassandra/cluster/schema/fetchers_spec.rb @@ -70,12 +70,17 @@ module Fetchers table.each_index do |index| parts << index.to_cql end + + table.each_trigger do |trigger| + parts << trigger.to_cql + end end keyspace.each_materialized_view do |view| parts << view.to_cql end end cql = parts.join("\n\n") + cql += "\n" expect(cql).to eq(File.read(File.dirname(__FILE__) + '/fetchers/' + version + '-schema.cql')) end From 40d10b944a2f6f55116da914a74203780b16288e Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Wed, 31 Aug 2016 15:59:41 -0700 Subject: [PATCH 122/196] RUBY-187 * Fixed flakey "test_cluster_session_inspect" test. --- integration/session_test.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/integration/session_test.rb b/integration/session_test.rb index 54bf1c1e2..fa79d9193 100644 --- a/integration/session_test.rb +++ b/integration/session_test.rb @@ -843,6 +843,8 @@ def test_can_set_protocol_version_explicitly # @test_category connection # def test_cluster_session_inspect + setup_schema + cluster = Cassandra.cluster(hosts: ['127.0.0.1'], consistency: :quorum, page_size: 10) session = cluster.connect('simplex') From 13876f4097b570b56dd03cd7dc49ed62e61b7669 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Fri, 2 Sep 2016 08:39:05 -0700 Subject: [PATCH 123/196] Updated README to reference new location in docs.ds. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index defb56151..419d97135 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Datastax Ruby Driver for Apache Cassandra -*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.3](https://github.com/datastax/ruby-driver/tree/v3.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/developer/ruby-driver/latest) or via the release tags, [e.g. v3.0.3](https://github.com/datastax/ruby-driver/tree/v3.0.3).* [![Build Status](https://travis-ci.org/datastax/ruby-driver.svg?branch=master)](https://travis-ci.org/datastax/ruby-driver) @@ -8,7 +8,7 @@ A Ruby client driver for Apache Cassandra. This driver works exclusively with the Cassandra Query Language version 3 (CQL3) and Cassandra's native protocol. - Code: https://github.com/datastax/ruby-driver -- Docs: http://docs.datastax.com/en/latest-ruby-driver/ruby-driver/whatsNew.html +- Docs: http://docs.datastax.com/en/developer/ruby-driver/latest - Jira: https://datastax-oss.atlassian.net/browse/RUBY - Mailing List: https://groups.google.com/a/lists.datastax.com/forum/#!forum/ruby-driver-user - IRC: #datastax-drivers on [irc.freenode.net](http://freenode.net>) From 34ba1c6ca2c691a769f7dc984ffb08aba7104008 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Fri, 2 Sep 2016 09:26:57 -0700 Subject: [PATCH 124/196] Updated docs.yaml to standardize on major.minor version names and reduce deps on datastax.github.io --- docs.yaml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs.yaml b/docs.yaml index aa4ae0ff4..b5ef1305a 100644 --- a/docs.yaml +++ b/docs.yaml @@ -1,6 +1,6 @@ title: Ruby Driver for Apache Cassandra summary: A pure Ruby client for Apache Cassandra -homepage: http://datastax.github.io/ruby-driver/ +homepage: http://docs.datastax.com/en/developer/ruby-driver/ sections: - title: Features prefix: /features @@ -27,7 +27,7 @@ links: - title: Code href: https://github.com/datastax/ruby-driver/ - title: Docs - href: http://datastax.github.io/ruby-driver/ + href: http://docs.datastax.com/en/developer/ruby-driver/ - title: Issues href: https://datastax-oss.atlassian.net/browse/RUBY/ - title: Mailing List @@ -37,15 +37,15 @@ links: - title: Releases href: https://github.com/datastax/ruby-driver/releases versions: - - name: v3.0.3 + - name: 3.0 ref: 6ec356a9c2082a4d1ae687e16385f34a56269f46 - - name: v2.1.6 + - name: 2.1 ref: 474568501e5ef771a496c3a901f3cbdc3854f512 - - name: v2.0.1 + - name: 2.0 ref: 80cab0ad188511ec16eb39111c0b67329c754729 - - name: v1.2.0 + - name: 1.2 ref: 6c398cb3dbc5218f6a77177dcc2130d11bf0158a - - name: v1.1.1 + - name: 1.1 ref: 1763066e2f70db8889799aa0af4a8eb63ad9ab74 - - name: v1.0.0 + - name: 1.0 ref: 72bfc9609f94e904c0186b061a02404fb2c0d22e From c72cd55797692153af4e868058123e8a016f7c5a Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Fri, 2 Sep 2016 13:20:10 -0700 Subject: [PATCH 125/196] Updated sha for latest 2.1 docs in docs.yaml --- docs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs.yaml b/docs.yaml index b5ef1305a..55ff4f56c 100644 --- a/docs.yaml +++ b/docs.yaml @@ -40,7 +40,7 @@ versions: - name: 3.0 ref: 6ec356a9c2082a4d1ae687e16385f34a56269f46 - name: 2.1 - ref: 474568501e5ef771a496c3a901f3cbdc3854f512 + ref: 1c450d3d3fc0ec449d5b2a8db9017915e77e8698 - name: 2.0 ref: 80cab0ad188511ec16eb39111c0b67329c754729 - name: 1.2 From a9b6bb5135057617289764c5ea8d42aac38f4ef9 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Fri, 2 Sep 2016 17:26:06 -0700 Subject: [PATCH 126/196] Refinements to README, changing datastax.github.io refs to docs.ds, among other things. --- CHANGELOG.md | 6 ++- README.md | 59 ++++++++++++++++-------------- features/debugging/README.md | 8 +++- features/load_balancing/README.md | 2 - features/state_listeners/README.md | 4 +- lib/cassandra/execution/options.rb | 2 +- 6 files changed, 48 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ff6508ed..635d22c1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -85,6 +85,10 @@ Breaking Changes: * Unavailable errors are retried on the next host in the load balancing plan by default. * Statement execution no longer retried on timeouts, unless `:idempotent => true` has been specified when executing. +# 2.1.7 +Bug Fixes: +* [RUBY-255](https://datastax-oss.atlassian.net/browse/RUBY-255) ControlConnection.peer_ip ignores peers that are missing critical information in system.peers. + # 2.1.6 Bug Fixes: @@ -105,7 +109,6 @@ Bug Fixes: Features: -* [RUBY-119](https://datastax-oss.atlassian.net/browse/RUBY-119) Use `require 'datastax/cassandra'` to avoid namespace conflicts * [RUBY-90](https://datastax-oss.atlassian.net/browse/RUBY-90) Add support for disabling nagle algorithm (tcp nodelay), enabled by default. * [RUBY-70](https://datastax-oss.atlassian.net/browse/RUBY-70) Add support for client-side timestamps, disabled by default. * [RUBY-114](https://datastax-oss.atlassian.net/browse/RUBY-114) Add support for serial consistency in batch requests. @@ -118,6 +121,7 @@ Bug Fixes: * [RUBY-97](https://datastax-oss.atlassian.net/browse/RUBY-97) Allow disabling of the initial population of schema metadata * [RUBY-95](https://datastax-oss.atlassian.net/browse/RUBY-95) Speed up generation of large token maps * [RUBY-116](https://datastax-oss.atlassian.net/browse/RUBY-116) fix thread leak on connection error +* [RUBY-119](https://datastax-oss.atlassian.net/browse/RUBY-119) Use `require 'datastax/cassandra'` to avoid namespace conflicts Breaking Changes: diff --git a/README.md b/README.md index 16f8300a1..90975fa24 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # Datastax Ruby Driver for Apache Cassandra -*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.3](https://github.com/datastax/ruby-driver/tree/v3.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 view the documentation for the latest released +version [here](http://docs.datastax.com/en/developer/ruby-driver/latest).* [![Build Status](https://travis-ci.org/datastax/ruby-driver.svg?branch=master)](https://travis-ci.org/datastax/ruby-driver) @@ -8,22 +10,22 @@ A Ruby client driver for Apache Cassandra. This driver works exclusively with the Cassandra Query Language version 3 (CQL3) and Cassandra's native protocol. - Code: https://github.com/datastax/ruby-driver -- Docs: http://docs.datastax.com/en/latest-ruby-driver/ruby-driver/whatsNew.html +- Docs: http://docs.datastax.com/en/developer/ruby-driver - Jira: https://datastax-oss.atlassian.net/browse/RUBY - Mailing List: https://groups.google.com/a/lists.datastax.com/forum/#!forum/ruby-driver-user - IRC: #datastax-drivers on [irc.freenode.net](http://freenode.net>) -- Twitter: Follow the latest news about DataStax Drivers - [@avalanche123](http://twitter.com/avalanche123), [@stamhankar999](http://twitter.com/stamhankar999), [@al3xandru](https://twitter.com/al3xandru) +- Twitter: Follow the latest news about DataStax Drivers - [@stamhankar999](http://twitter.com/stamhankar999), [@avalanche123](http://twitter.com/avalanche123), [@al3xandru](https://twitter.com/al3xandru) 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: -* [Asynchronous execution](http://docs.datastax.com/en/developer/ruby-driver/3.0/supplemental/features/asynchronous_io/) -* 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/) -* 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/) -* 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/) -* [SSL encryption](http://docs.datastax.com/en/developer/ruby-driver/3.0/supplemental/features/security/ssl_encryption/) -* [Flexible and robust error handling](http://docs.datastax.com/en/developer/ruby-driver/3.0/supplemental/features/error_handling/) -* [Per-request execution information and tracing](http://docs.datastax.com/en/developer/ruby-driver/3.0/supplemental/features/debugging/) -* [Configurable address resolution](http://docs.datastax.com/en/developer/ruby-driver/3.0/supplemental/features/address_resolution/) +* [Asynchronous execution](http://docs.datastax.com/en/developer/ruby-driver/3.0/features/asynchronous_io/) +* One-off, [prepared](http://docs.datastax.com/en/developer/ruby-driver/3.0/features/basics/prepared_statements/) and [batch statements](http://docs.datastax.com/en/developer/ruby-driver/3.0/features/basics/batch_statements/) +* Automatic peer discovery and cluster metadata with [support for change notifications](http://docs.datastax.com/en/developer/ruby-driver/3.0/features/state_listeners/) +* Various [load-balancing](http://docs.datastax.com/en/developer/ruby-driver/3.0/features/load_balancing/), [retry](http://docs.datastax.com/en/developer/ruby-driver/3.0/features/retry_policies/) and [reconnection](http://docs.datastax.com/en/developer/ruby-driver/3.0/features/reconnection/) policies with [ability to write your own](http://docs.datastax.com/en/developer/ruby-driver/3.0/features/load_balancing/implementing_a_policy/) +* [SSL encryption](http://docs.datastax.com/en/developer/ruby-driver/3.0/features/security/ssl_encryption/) +* [Flexible and robust error handling](http://docs.datastax.com/en/developer/ruby-driver/3.0/features/error_handling/) +* [Per-request execution information and tracing](http://docs.datastax.com/en/developer/ruby-driver/3.0/features/debugging/) +* [Configurable address resolution](http://docs.datastax.com/en/developer/ruby-driver/3.0/features/address_resolution/) [Check out the slides from Ruby Driver Explained](https://speakerdeck.com/avalanche123/ruby-driver-explained) for a detailed overview of the Ruby Driver architecture. @@ -36,10 +38,14 @@ This driver works exclusively with the Cassandra Query Language v3 (CQL3) and Ca * Ruby (MRI) 2.2, 2.3 * JRuby 1.7 -__Note__: JRuby 1.6 is not officially supported, although 1.6.8 should work. Similarly, -MRI 2.0 and 2.1 are not officially supported, but they should work. 1.9.3 is deprecated +__Note__: JRuby 1.6 is not officially supported, although 1.6.8 should work. Rubinius is not supported. +MRI 2.0, 2.1, and JRuby 9k are not officially supported, but they should work. 1.9.3 is deprecated and is likely to break in the release following 3.0. +## Feedback Requested + +*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). + ## Quick start ```ruby @@ -51,10 +57,10 @@ cluster.each_host do |host| # automatically discovers all peers puts "Host #{host.ip}: id=#{host.id} datacenter=#{host.datacenter} rack=#{host.rack}" end -keyspace = 'system' +keyspace = 'system_schema' session = cluster.connect(keyspace) # create session, optionally scoped to a keyspace, to execute queries -future = session.execute_async('SELECT keyspace_name, table_name FROM system_schema.tables') # fully asynchronous api +future = session.execute_async('SELECT keyspace_name, table_name FROM tables') # fully asynchronous api future.on_success do |rows| rows.each do |row| puts "The keyspace #{row['keyspace_name']} has a table called #{row['table_name']}" @@ -67,9 +73,9 @@ __Note__: The host you specify is just a seed node, the driver will automaticall Read more: -* [`Cassandra.cluster` options](http://docs.datastax.com/en/developer/ruby-driver/3.0/supplemental/api/cassandra/#cluster-class_method) -* [`Session#execute_async` options](http://docs.datastax.com/en/developer/ruby-driver/3.0/supplemental/api/cassandra/session/#execute_async-instance_method) -* [Usage documentation](http://docs.datastax.com/en/developer/ruby-driver/3.0/supplemental/features) +* [`Cassandra.cluster` options](http://docs.datastax.com/en/developer/ruby-driver/3.0/api/cassandra/#cluster-class_method) +* [`Session#execute_async` options](http://docs.datastax.com/en/developer/ruby-driver/3.0/api/cassandra/session/#execute_async-instance_method) +* [Usage documentation](http://docs.datastax.com/en/developer/ruby-driver/3.0/features) ## Installation @@ -85,12 +91,15 @@ Install via Gemfile gem 'cassandra-driver' ``` -__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) +__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/features/#compression) ## Upgrading from cql-rb -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.3/examples/cql-rb-wrapper.rb) to assist you with gradual upgrade. +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.3/examples/cql-rb-wrapper.rb) +to assist you with gradual upgrade. ## What's new in v3.0 @@ -150,10 +159,6 @@ batch.add(query, arguments: {p1: 'val1'}) * [[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. * [[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. -## Feedback Requested - -*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). - ## Code examples The DataStax Ruby Driver uses the awesome [Cucumber Framework](http://cukes.info/) for @@ -212,7 +217,7 @@ the release. * Because the driver reactor is using `IO.select`, the maximum number of tcp connections allowed is 1024. * Because the driver uses `IO#write_nonblock`, Windows is not supported. -Please [refer to the usage documentation for more information on common pitfalls](http://docs.datastax.com/en/developer/ruby-driver/3.0/supplemental/features/) +Please [refer to the usage documentation for more information on common pitfalls](http://docs.datastax.com/en/developer/ruby-driver/3.0/features/) ## Contributing @@ -226,7 +231,7 @@ that are common across all other DataStax drivers for Apache Cassandra. The development effort to provide an up to date, high performance, fully featured Ruby Driver for Apache Cassandra will continue on this project, while -[cql-rb](https://github.com/iconara/cql-rb/) will be discontinued. +[cql-rb](https://github.com/iconara/cql-rb/) has been discontinued. ## Copyright @@ -242,4 +247,4 @@ License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF either express or implied. See the License for the specific language governing permissions and limitations under the License. - [1]: http://docs.datastax.com/en/developer/ruby-driver/3.0/supplemental/api + [1]: http://docs.datastax.com/en/developer/ruby-driver/3.0/api diff --git a/features/debugging/README.md b/features/debugging/README.md index 948532978..cc8a4b70d 100644 --- a/features/debugging/README.md +++ b/features/debugging/README.md @@ -1 +1,7 @@ -# Debugging \ No newline at end of file +# Debugging + +Execution info can be used to dig into details on how requests executed. If request tracing is enabled, the +trace info will be available in the exection info. + +In addition, configuring the logger for the cluster object will result in extra debug output emitted that can help +track down issues. diff --git a/features/load_balancing/README.md b/features/load_balancing/README.md index 13b82acbe..1c467e77c 100644 --- a/features/load_balancing/README.md +++ b/features/load_balancing/README.md @@ -28,5 +28,3 @@ Here is a high level diagram of how a load balancing policy is used during query |<-----------------| | | | | | ``` - -[Check out the Ruby Driver - Load Balancing Screencast](https://academy.datastax.com/demos/datastax-ruby-driver-load-balancing-policies) for a quick and in-depth guide to load balancing with the Ruby Driver. diff --git a/features/state_listeners/README.md b/features/state_listeners/README.md index 46a41b3ca..765f7e5b3 100644 --- a/features/state_listeners/README.md +++ b/features/state_listeners/README.md @@ -1 +1,3 @@ -# State Listeners \ No newline at end of file +# State Listeners + +You can define listeners for schema change events as well as cluster membership changes. diff --git a/lib/cassandra/execution/options.rb b/lib/cassandra/execution/options.rb index d0499e053..cea5060b3 100644 --- a/lib/cassandra/execution/options.rb +++ b/lib/cassandra/execution/options.rb @@ -54,7 +54,7 @@ class Options # # @see https://github.com/apache/cassandra/blob/cassandra-3.4/doc/native_protocol_v4.spec#L125-L131 Description # of custom payload in Cassandra native protocol v4. - # @see http://docs.datastax.com/en/developer/java-driver/3.0/supplemental/manual/custom_payloads/?local=true&nav=toc#enabling-custom-payloads-on-c-nodes + # @see http://docs.datastax.com/en/developer/java-driver/3.0/manual/custom_payloads/#enabling-custom-payloads-on-c-nodes # Enabling custom payloads on Cassandra nodes. # # @example Sending a custom payload From 50efcfea453172987ade4a59b59b2ac4b2a244b2 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Fri, 2 Sep 2016 17:32:40 -0700 Subject: [PATCH 127/196] Updated docs.yaml with new sha's for 3.0 and 2.1 docs --- docs.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs.yaml b/docs.yaml index 55ff4f56c..ca2390b2c 100644 --- a/docs.yaml +++ b/docs.yaml @@ -38,9 +38,9 @@ links: href: https://github.com/datastax/ruby-driver/releases versions: - name: 3.0 - ref: 6ec356a9c2082a4d1ae687e16385f34a56269f46 + ref: a9b6bb5135057617289764c5ea8d42aac38f4ef9 - name: 2.1 - ref: 1c450d3d3fc0ec449d5b2a8db9017915e77e8698 + ref: v2.1.7 - name: 2.0 ref: 80cab0ad188511ec16eb39111c0b67329c754729 - name: 1.2 From 234001a0efa692b53967721f2fab394ec1b32a1c Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Fri, 2 Sep 2016 17:57:32 -0700 Subject: [PATCH 128/196] Updated docs.yaml 3.0 version to point to v3.0.3 tag --- docs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs.yaml b/docs.yaml index ca2390b2c..dbd646b0d 100644 --- a/docs.yaml +++ b/docs.yaml @@ -38,7 +38,7 @@ links: href: https://github.com/datastax/ruby-driver/releases versions: - name: 3.0 - ref: a9b6bb5135057617289764c5ea8d42aac38f4ef9 + ref: v3.0.3 - name: 2.1 ref: v2.1.7 - name: 2.0 From 247a3324fd235ae99b550f6362b3b3cc88b8b599 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 12 Sep 2016 15:44:17 -0700 Subject: [PATCH 129/196] RUBY-164 - don't mark host down after receiving a host_down event if there are open connections. --- CHANGELOG.md | 4 ++++ lib/cassandra/cluster/control_connection.rb | 4 +++- spec/cassandra/cluster/control_connection_spec.rb | 5 ++--- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0730f193..48591812a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,11 @@ # master Features: +* Do not mark a host as down if there are active connections. +* Update Keyspace metadata to include collection of indexes defined in the keyspace. +* Update Table metadata to include trigger-collection and view-collection metadata. Bug Fixes: +* [RUBY-255](https://datastax-oss.atlassian.net/browse/RUBY-255) ControlConnection.peer_ip ignores peers that are missing critical information in system.peers. # 3.0.3 diff --git a/lib/cassandra/cluster/control_connection.rb b/lib/cassandra/cluster/control_connection.rb index 022134a48..39b5fefa3 100644 --- a/lib/cassandra/cluster/control_connection.rb +++ b/lib/cassandra/cluster/control_connection.rb @@ -229,7 +229,9 @@ def register_async refresh_schema_async_wrapper end when 'DOWN' - @registry.host_down(event.address) + # RUBY-164: Don't mark host down if there are active connections. We have + # logic in connector.rb to call host_down when all connections to a node are lost, + # so that covers the requirement. when 'NEW_NODE' address = event.address diff --git a/spec/cassandra/cluster/control_connection_spec.rb b/spec/cassandra/cluster/control_connection_spec.rb index 703313f0f..70782250e 100644 --- a/spec/cassandra/cluster/control_connection_spec.rb +++ b/spec/cassandra/cluster/control_connection_spec.rb @@ -895,9 +895,8 @@ def handle_request(&handler) logger.should have_received(:debug).with(/Event received EVENT STATUS_CHANGE DOWN/) end - it 'notifies registry' do - ip = address.to_s - expect(cluster_registry).to receive(:host_down).once.with(ip) + it 'does not notify registry' do + expect(cluster_registry).to_not receive(:host_down) connections.first.trigger_event(event) end end From 4ccd95b2c939e98579bf8bbbc7b7d8ac08504b25 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Wed, 14 Sep 2016 12:58:47 -0700 Subject: [PATCH 130/196] RUBY-164 - don't mark host down after receiving a host_down event if there are open connections. * Fixed broken cuke test. --- features/state_listeners/membership_changes.feature | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/features/state_listeners/membership_changes.feature b/features/state_listeners/membership_changes.feature index cb462996a..ffed40c5c 100644 --- a/features/state_listeners/membership_changes.feature +++ b/features/state_listeners/membership_changes.feature @@ -1,7 +1,7 @@ Feature: Membership change detection Cluster object allows registering state listeners. It then guarantees that - they will be notifies on cluster membership changes. + they will be notified on cluster membership changes. Background: Given a running cassandra cluster @@ -39,6 +39,8 @@ Feature: Membership change detection cluster.register(listener) + session = cluster.connect + $stdout.puts("=== START ===") $stdout.flush $stdin.gets From 3f31312ba62242b3c1495786b89b946647ecdfc7 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Wed, 14 Sep 2016 16:10:04 -0700 Subject: [PATCH 131/196] RUBY-164 - don't mark host down after receiving a host_down event if there are open connections. * Fixed another broken cuke test. --- features/state_listeners/membership_changes.feature | 3 +++ 1 file changed, 3 insertions(+) diff --git a/features/state_listeners/membership_changes.feature b/features/state_listeners/membership_changes.feature index ffed40c5c..1bbfa27a5 100644 --- a/features/state_listeners/membership_changes.feature +++ b/features/state_listeners/membership_changes.feature @@ -71,6 +71,9 @@ Feature: Membership change detection """ Host 127.0.0.4 is found Host 127.0.0.4 is up + """ + And its output should contain: + """ Host 127.0.0.4 is down Host 127.0.0.4 is lost """ From 7b302f8ea35432020808a3329592d50085f5129d Mon Sep 17 00:00:00 2001 From: Kishan Karunaratne Date: Wed, 14 Sep 2016 16:17:33 -0700 Subject: [PATCH 132/196] [RUBY-187] Integration tests and cuke features for trigger meta --- features/basics/schema_metadata.feature | 33 ++++++++++++++++ integration/metadata_test.rb | 46 ++++++++++++++++++++++- integration/schema_change_listener.rb | 7 ++++ support/ccm.rb | 13 +++++++ support/triggers/AuditTrigger.properties | 20 ++++++++++ support/triggers/trigger-example.jar | Bin 0 -> 3312 bytes 6 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 support/triggers/AuditTrigger.properties create mode 100644 support/triggers/trigger-example.jar diff --git a/features/basics/schema_metadata.feature b/features/basics/schema_metadata.feature index d439a010d..3f21124e7 100644 --- a/features/basics/schema_metadata.feature +++ b/features/basics/schema_metadata.feature @@ -80,6 +80,39 @@ Feature: Schema Metadata ) """ + Scenario: Getting trigger metadata + Given the following schema: + """cql + CREATE KEYSPACE simplex WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 3}; + CREATE TABLE simplex.test_table (a text primary key, b text); + CREATE TABLE simplex.audit (key timeuuid, keyspace_name text, table_name text, primary_key text, PRIMARY KEY(key)); + CREATE TRIGGER trigger1 ON simplex.test_table USING 'org.apache.cassandra.triggers.AuditTrigger'; + """ + And the following example: + """ruby + require 'cassandra' + + cluster = Cassandra.cluster + + # AuditTrigger is available in cassandra/conf/triggers. See cassandra/examples/triggers in Cassandra + trigger = cluster.keyspace('simplex').table('test_table').trigger('trigger1') + puts trigger.to_cql + + puts "" + puts "Name: #{trigger.name}" + puts "Table name: #{trigger.table.name}" + puts "Class: #{trigger.options['class']}" + """ + When it is executed + Then its output should contain: + """cql + CREATE TRIGGER "trigger1" ON simplex.test_table USING 'org.apache.cassandra.triggers.AuditTrigger'; + + Name: trigger1 + Table name: test_table + Class: org.apache.cassandra.triggers.AuditTrigger + """ + Scenario: Getting index metadata Given the following schema: """cql diff --git a/integration/metadata_test.rb b/integration/metadata_test.rb index 7ad2701e2..747ccaba1 100644 --- a/integration/metadata_test.rb +++ b/integration/metadata_test.rb @@ -39,6 +39,12 @@ def setup @session.execute("CREATE TABLE simplex.custom (f1 int PRIMARY KEY," \ " f2 'org.apache.cassandra.db.marshal.CompositeType(org.apache.cassandra.db.marshal.UUIDType,org.apache.cassandra.db.marshal.UTF8Type)')") @listener.wait_for_table('simplex', 'custom') + @session.execute("CREATE TABLE simplex.test1 (key text, value text, PRIMARY KEY(key))") + @listener.wait_for_table('simplex', 'test1') + @session.execute("CREATE TABLE simplex.test2 (key text, value text, PRIMARY KEY(key))") + @listener.wait_for_table('simplex', 'test2') + @session.execute("CREATE TABLE simplex.audit (key timeuuid, keyspace_name text, table_name text, primary_key text, PRIMARY KEY(key))") + @listener.wait_for_table('simplex', 'audit') end def teardown @@ -63,7 +69,7 @@ def test_can_retrieve_keyspace_metadata assert_equal 1, ks_meta.replication.options['replication_factor'].to_i assert ks_meta.durable_writes? assert ks_meta.has_table?('users') - assert_equal 4, ks_meta.tables.size + assert_equal 7, ks_meta.tables.size ks_cql = Regexp.new(/CREATE KEYSPACE simplex WITH replication = {'class': 'SimpleStrategy', \ 'replication_factor': '1'} AND durable_writes = true;/) @@ -342,4 +348,42 @@ def test_table_metadata_contains_extensions assert_empty table_meta.options.extensions end + # Test for retrieving trigger metadata + # + # test_can_retrieve_trigger_metadata tests that all pieces of trigger metadata can be retrieved. It first creates a + # simple trigger. It then goes through each piece of the trigger metadata and verifies that each piece is as expected. + # It finally creates another trigger with the same name, on a different table and verifies that it is retrieved and + # complete. + # + # @since 3.1.0 + # @jira_ticket RUBY-187 + # @expected_result trigger metadata should be retrieved. + # + # @test_category metadata + # + def test_can_retrieve_trigger_metadata + skip("Triggers were introduced in Cassandra 2.0") if CCM.cassandra_version < '2.0.0' + + # trigger1, on test1 table + @session.execute("CREATE TRIGGER trigger1 ON simplex.test1 USING 'org.apache.cassandra.triggers.AuditTrigger'") + @listener.wait_for_trigger('simplex', 'test1', 'trigger1') + + assert @cluster.keyspace('simplex').table('test1').has_trigger?('trigger1') + trigger_meta = @cluster.keyspace('simplex').table('test1').trigger('trigger1') + assert_equal 'trigger1', trigger_meta.name + assert_equal 'test1', trigger_meta.table.name + assert_equal 'org.apache.cassandra.triggers.AuditTrigger', trigger_meta.options['class'] + + # trigger1, on test2 table + @session.execute("CREATE TRIGGER trigger1 ON simplex.test2 USING 'org.apache.cassandra.triggers.AuditTrigger'") + @listener.wait_for_trigger('simplex', 'test2', 'trigger1') + + assert @cluster.keyspace('simplex').table('test2').has_trigger?('trigger1') + trigger_meta2 = @cluster.keyspace('simplex').table('test2').trigger('trigger1') + assert_equal 'trigger1', trigger_meta2.name + assert_equal 'test2', trigger_meta2.table.name + assert_equal 'org.apache.cassandra.triggers.AuditTrigger', trigger_meta2.options['class'] + + refute_equal trigger_meta, trigger_meta2 + end end diff --git a/integration/schema_change_listener.rb b/integration/schema_change_listener.rb index 1264b7333..76b1b7f5f 100644 --- a/integration/schema_change_listener.rb +++ b/integration/schema_change_listener.rb @@ -73,6 +73,13 @@ def wait_for_materialized_view(keyspace_name, view_name, *args) end end + def wait_for_trigger(keyspace_name, table_name, trigger_name, *args) + self.wait_for_table(keyspace_name, table_name) + wait_for_change(keyspace_name, 2) do |ks| + ks.table(table_name).has_trigger?(trigger_name, *args) + end + end + def keyspace_changed(keyspace) # This looks a little strange, but here's the idea: if we don't have # Condition's for this keyspace, immediately return. Otherwise, for each diff --git a/support/ccm.rb b/support/ccm.rb index 7b6ce72f6..a5ddf8d5f 100644 --- a/support/ccm.rb +++ b/support/ccm.rb @@ -455,6 +455,8 @@ def start(jvm_arg=nil) options[:load_balancing_policy] = SameOrderLoadBalancingPolicy.new + enable_triggers + total_attempts = 1 until @nodes.all?(&:up?) && @cluster && @cluster.hosts.select(&:up?).count == @nodes.size attempts = 1 @@ -714,6 +716,17 @@ def change_tombstone_thresholds start end + def enable_triggers + trigger_root = File.expand_path(File.dirname(__FILE__) + '/../support/triggers') + ccm_node_conf_dir = "~/.ccm/ruby-driver-cassandra-#{CCM.cassandra_version}-test-cluster" + + (1..@nodes.size).each do |n| + `mkdir #{ccm_node_conf_dir}/node#{n}/conf/triggers` + `cp #{trigger_root}/AuditTrigger.properties #{ccm_node_conf_dir}/node#{n}/conf` + `cp #{trigger_root}/trigger-example.jar #{ccm_node_conf_dir}/node#{n}/conf/triggers` + end + end + def setup_schema(schema) schema.strip! schema.chomp!(";") diff --git a/support/triggers/AuditTrigger.properties b/support/triggers/AuditTrigger.properties new file mode 100644 index 000000000..a8ccc51e9 --- /dev/null +++ b/support/triggers/AuditTrigger.properties @@ -0,0 +1,20 @@ +# encoding: utf-8 + +#-- +# Copyright 2013-2016 DataStax, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + +keyspace=simplex +table=audit diff --git a/support/triggers/trigger-example.jar b/support/triggers/trigger-example.jar new file mode 100644 index 0000000000000000000000000000000000000000..fca6515d794713ede3c6a0d9d1dd76427f1677bc GIT binary patch literal 3312 zcmaJ^c|26>A3n%7S+Y!)sGBSq4P|U0vW;bC3L}&yljbtkF=LIJ$dWa(C8M&0i?|_s zS;kyTgRI$Zma#+;S8gHhZ=|Sxb-(BHzUTb$JkR(0en0Pd&vT3sOnX28zy<(@A<~Ax zSHTYK0Z@8oT2R9?aJkJk05JXxWdjW8(1OFOBNym!2m0Wrj~ysV>x>~>4{aukf-j?} z&yWbHY!@d23hAJBmYN>#g^f)Ss5-nJ#|)4i2IqiC1_|g2)vt;aWy*&zMSG@0wC37Z zI40VmUI`p1u)@bE-UOm4n6w-P`a6mjacxb}ugbS-rxxH-EnihlFB$;c2@Y~!s{Mb& z-p>dO9%Ju_{SP4cTcACLNW{2ZCSd-9a`0yqPXf-t0ZSn6aJRcihXagT6Fv6R$4(cu zv2ON`t{8$d5=Zo8+F9>5i;Lxin-U!z{XI*^-4%~>!4eE`Zk|L$Yo|;r^;s(w(bv`8 z6M2@}8qwL-&L%63nlREzfFR@=BrHg`t>O|Q(dB|ENJF>@L!g?yf?Y3iQZCYNJah_(|E0wi(q4(>d1Ss`p`VFjz=w$EZ$Pt|_mp@-TDuvMh$6riCh)-w~6>SNg`Itw_ z{C91>kWaZ9Ia}Aamy9Xq&&lG!h(aCvJWgqYf)h&LXVA3NbyNKPUW6C*(;x@?m)H*W zEeRg9SPF@iT1t$}^-{m?@Su8z&G$^}4T=w-Y4+E>*5l`o(B@4i;eYQ7c}f7O@Gtg0GK`Vfu&C3cN-#uii>M@B*C z0r0)q4YJByBvS%GJ~0>a;d3#AG7KuR6x^^#MaPFrC=g7tC4Z7S|Zw9e~2YI1d^ ziVOUXYfzDgzTiuyzD}p~V7nNED7rmeV3>NBw7StN>EK+!#;K6bXU@`7IzmEj*#*DF zWu@MX5%!bTz1Pu1t+cPX8meL2wpe?up<$sTD=f)p4(LQMIuB(grYJC;w9W)UnSWEi zf<0Q+V%`-1)r1SC)A(|eeq)~ZEIINl4`!J)P57OM<#|Ab>u8TbQk_5h&+du^hi zIhJ+yjD>hOjQa=3&5rH=~p%FermGWoTN1RoT^Hso_do1WMeT$^s= zOooAGVb*n}kE@7!MUOlgth(iyN|c|)T6SBQg{?`+KOEfexGk(CJj$KI9aB@d*SQNU-VCJ`c;O+kb5c@nuJ~2FvR&}k#aa0by7j0-EP>g zmm-U4#xE;)+b+Z>o=PDL_?B_4IM#aya-fk+p&L+9DKhDpMWNO@odnapEWq$z>Vl_D5f|3Hi;6YAC!26^D>VN(f5g zMya{;t-6t5I%;Ng;_a;db{%*dgu%M-;-gz(xx~$Pn6QkI^@5o*}Id`@E5kZ~xQP#x)+Wb|{y62oM*9NJY$`zwh zcp13qkcs;$QF%$fBmKI0i&G68l;pqo=FK6dJx|-!-ci`2qgq*K38&zV%vSXgPBYc+ z>l?>8GH#cJ4VkS|MmB{aBxlFDu1M8KISpJ0c~_Id(rOFm6`h=%V2qCu3~)uOp=ZYq zaS+V=xa?})JzbcF73du}R?Hfgyhw4$Oq}r4(acmh z?%c@}9TVu_iX(ch2d-(-CO!0{vrji2N?+po1-&LxWoh#~v;I)aW07GGSwt%NPW}~> z6X({r4@|wyvhOi^{kqK9a!l;PY!0mC^yZ8VN3hR9sP?pfb3b#3gSXrjkxPs8oxs4z z&-8QWNZ$_h*aysOZQXZl&9;yHLjAFm+#+m6c90Bx-l2ZwE5A;@d-eKhD4I=>Px# literal 0 HcmV?d00001 From a642aa833aa7e4a7d6d18798e0267d542f09021b Mon Sep 17 00:00:00 2001 From: Kishan Karunaratne Date: Wed, 21 Sep 2016 15:43:29 -0700 Subject: [PATCH 133/196] Add schedules to CI --- build.yaml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/build.yaml b/build.yaml index 64cd5a76a..75fe0edf8 100644 --- a/build.yaml +++ b/build.yaml @@ -1,8 +1,33 @@ +schedules: + commit: + schedule: per_commit + matrix: + exclude: + - ruby: ['2.2', 'jruby9k'] + - cassandra: ['1.2', '2.0', '2.2', '3.0'] + nightly: + schedule: nightly + branches: + include: [master] + matrix: + exclude: + - ruby: jruby9k + - cassandra: 1.2 + weekly: + schedule: weekly + branches: + include: [master] + matrix: + exclude: + - ruby: jruby9k + ruby: - 2.2 - 2.3 - jruby1.7 + - jruby9k cassandra: + - 1.2 - 2.0 - 2.1 - 2.2 From af234e167cf8229ea52b9d785bfb5a2d6e73da96 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Fri, 23 Sep 2016 12:47:33 -0700 Subject: [PATCH 134/196] RUBY-256: Execution profiles * Added functionality, unit tests, and a happy-path cucumber feature. --- features/basics/execution_profiles.feature | 56 ++++++ integration/execution_profiles_test.rb | 73 +++++++ lib/cassandra.rb | 81 +++++++- lib/cassandra/cluster.rb | 17 +- lib/cassandra/cluster/client.rb | 28 ++- lib/cassandra/driver.rb | 32 ++- lib/cassandra/execution/options.rb | 65 ++++-- lib/cassandra/execution/profile.rb | 88 ++++++++ lib/cassandra/execution/profile_manager.rb | 85 ++++++++ lib/cassandra/execution/trace.rb | 9 +- lib/cassandra/result.rb | 2 +- lib/cassandra/session.rb | 61 ++++-- lib/cassandra/statements/prepared.rb | 2 +- spec/cassandra/cluster/client_spec.rb | 86 ++++---- spec/cassandra/cluster_spec.rb | 2 +- spec/cassandra/execution/options_spec.rb | 49 ++++- .../execution/profile_manager_spec.rb | 88 ++++++++ spec/cassandra/execution/trace_spec.rb | 23 ++- .../policies/dc_aware_round_robin_spec.rb | 11 +- spec/cassandra/session_spec.rb | 69 ++++++- spec/cassandra_spec.rb | 190 +++++++++++++----- spec/regressions/RUBY-189_spec.rb | 2 +- spec/support/fake_cluster_registry.rb | 18 ++ 23 files changed, 958 insertions(+), 179 deletions(-) create mode 100644 features/basics/execution_profiles.feature create mode 100644 integration/execution_profiles_test.rb create mode 100644 lib/cassandra/execution/profile.rb create mode 100644 lib/cassandra/execution/profile_manager.rb create mode 100644 spec/cassandra/execution/profile_manager_spec.rb diff --git a/features/basics/execution_profiles.feature b/features/basics/execution_profiles.feature new file mode 100644 index 000000000..ef6e62e2b --- /dev/null +++ b/features/basics/execution_profiles.feature @@ -0,0 +1,56 @@ +Feature: Execution profiles + + Execution profiles allow a user to group various execution options into a 'profile'. A user can then execute + statements with different profiles by specifying the profile name. + + Background: + Given a running cassandra cluster + + Scenario: Configure different load balancing policies with profiles. + Given the following example: + """ruby + require 'cassandra' + + include Cassandra::LoadBalancing::Policies + profiles = { + p1: Cassandra::Execution::Profile.new(load_balancing_policy: WhiteList.new(['127.0.0.1'], RoundRobin.new)), + p2: Cassandra::Execution::Profile.new(load_balancing_policy: WhiteList.new(['127.0.0.2'], RoundRobin.new)) + } + cluster = Cassandra.cluster(execution_profiles: profiles) + session = cluster.connect + + puts "Running with default profile" + 3.times do + rs = session.execute('select rpc_address from system.local') + puts rs.first['rpc_address'] + end + + puts "Running with profile p1" + 3.times do + rs = session.execute('select rpc_address from system.local', execution_profile: :p1) + puts rs.first['rpc_address'] + end + + puts "Running with profile p2" + 3.times do + rs = session.execute('select rpc_address from system.local', execution_profile: :p2) + puts rs.first['rpc_address'] + end + """ + When it is executed + Then its output should contain: + """ + Running with default profile + 127.0.0.2 + 127.0.0.3 + 127.0.0.1 + Running with profile p1 + 127.0.0.1 + 127.0.0.1 + 127.0.0.1 + Running with profile p2 + 127.0.0.2 + 127.0.0.2 + 127.0.0.2 + """ + diff --git a/integration/execution_profiles_test.rb b/integration/execution_profiles_test.rb new file mode 100644 index 000000000..15d0d86f6 --- /dev/null +++ b/integration/execution_profiles_test.rb @@ -0,0 +1,73 @@ +# encoding: utf-8 + +#-- +# Copyright 2013-2016 DataStax, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + +require File.dirname(__FILE__) + '/integration_test_case.rb' + +class ExecutionProfilesTest < IntegrationTestCase + def self.before_suite + @@ccm_cluster = CCM.setup_cluster(1, 2) + end + + def setup_schema + @@ccm_cluster.setup_schema(<<-CQL) + CREATE KEYSPACE simplex WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}; + USE simplex; + CREATE TABLE users (user_id BIGINT PRIMARY KEY, first VARCHAR, last VARCHAR, age BIGINT); + CREATE TABLE test (k text, v int, PRIMARY KEY (k, v)); + CQL + end + + def make_profile_without(attr) + profile_hash = { + consistency: :one, + load_balancing_policy: Cassandra::LoadBalancing::Policies::RoundRobin.new, + retry_policy: Cassandra::Retry::Policies::Default.new, + timeout: nil + } + profile_hash.delete(attr) + Cassandra::Execution::Profile.new(profile_hash) + end + + # Test that the default execution profile requires load-balancing policy, retry-policy, and consistency to be set. + # + # test_default_execution_profile_validation tests that when the default execution profile is overridden, + # load-balancing policy, retry-policy, and consistency are set. First, it creates a cluster with a legal + # override of the default profile. Then it attempts to create cluster objects with each of the required + # default-profile attributes missing and verifies that an ArgumentError occurs. + # + # @expected_errors [::ArgumentError] When a required attribute is missing in the default execution profile. + # + # @jira_ticket RUBY-256 + # @expected_result Successful cluster creation when all required profile attributes are set, errors otherwise. + # + # @test_assumptions A Cassandra cluster with at least one node. + # @test_category argument:validation + def test_default_execution_profile_validation + profiles = {Cassandra::DEFAULT_EXECUTION_PROFILE => make_profile_without(nil)} + cluster = Cassandra.cluster(execution_profiles: profiles) + cluster.close + + # Now try without a consistency, load_balancing_policy, retry_policy. + [:load_balancing_policy, :retry_policy, :consistency].each do |attr| + profiles = { Cassandra::DEFAULT_EXECUTION_PROFILE => make_profile_without(attr) } + assert_raises(ArgumentError, "removing #{attr} should raise") do + Cassandra.cluster(execution_profiles: profiles) + end + end + end +end diff --git a/lib/cassandra.rb b/lib/cassandra.rb index 868f2a206..49a211453 100644 --- a/lib/cassandra.rb +++ b/lib/cassandra.rb @@ -46,6 +46,10 @@ module Cassandra # @see Cassandra::Session#execute_async SERIAL_CONSISTENCIES = [:serial, :local_serial].freeze + # Name of the default execution profile. Use this constant as the key for an execution profile when initializing + # a {Cluster} to override the default execution profile with your own. + DEFAULT_EXECUTION_PROFILE = '__DEFAULT_EXECUTION_PROFILE__' + # A list of all possible write types that a # {Cassandra::Errors::WriteTimeoutError} can have. # @@ -68,6 +72,7 @@ module Cassandra :credentials, :custom_types, :datacenter, + :execution_profiles, :futures_factory, :heartbeat_interval, :hosts, @@ -117,10 +122,14 @@ module Cassandra # found by the default Token-Aware Load Balancing Policy should be # shuffled. See {Cassandra::LoadBalancing::Policies::TokenAware#initialize Token-Aware Load Balancing Policy}. # + # @option options [Hash] :execution_profiles (nil) + # Hash of {Cassandra::Execution::Profile}s that are available for client use (e.g. + # {Session#execute}, {Session#execute_async}, {Session#prepare}, and {Session#prepare_async}). + # # @option options [Numeric] :connect_timeout (10) connection timeout in # seconds. Setting value to `nil` will reset it to 5 seconds. # - # @option options [Numeric] :timeout (10) request execution timeout in + # @option options [Numeric] :timeout (12) request execution timeout in # seconds. Setting value to `nil` will remove request timeout. # # @option options [Numeric] :heartbeat_interval (30) how often should a @@ -503,14 +512,29 @@ def self.validate_and_massage_options(options) end end + if options.key?(:execution_profiles) + Util.assert_instance_of(::Hash, options[:execution_profiles], + ':execution_profiles must be a hash of entries.') + end + if options.key?(:timeout) timeout = options[:timeout] unless timeout.nil? - Util.assert_instance_of(::Numeric, timeout) do - ":timeout must be a number of seconds, #{timeout.inspect} given" + Util.assert(options[:execution_profiles].nil?, ":timeout cannot be specified when using execution profiles") + Util.assert_instance_of(::Numeric, timeout, ":timeout must be a number of seconds, #{timeout.inspect} given") + Util.assert(timeout > 0, ":timeout must be greater than 0, #{timeout} given") + end + end + if options.key?(:execution_profiles) + options[:execution_profiles].each do |name, profile| + timeout = profile.timeout + unless timeout.nil? + Util.assert_instance_of(::Numeric, timeout, + ":timeout of execution profile #{name} must be a number of seconds, " \ + "#{timeout.inspect} given") + Util.assert(timeout > 0, ":timeout of execution profile #{name} must be greater than 0, #{timeout} given") end - Util.assert(timeout > 0) { ":timeout must be greater than 0, #{timeout} given" } end end @@ -564,14 +588,27 @@ def self.validate_and_massage_options(options) if options.key?(:load_balancing_policy) load_balancing_policy = options[:load_balancing_policy] + Util.assert(options[:execution_profiles].nil?, ":load_balancing_policy cannot be specified when using execution profiles") methods = [:host_up, :host_down, :host_found, :host_lost, :setup, :teardown, :distance, :plan] - Util.assert_responds_to_all(methods, load_balancing_policy) do ":load_balancing_policy #{load_balancing_policy.inspect} must respond " \ "to #{methods.inspect}, but doesn't" end end + if options.key?(:execution_profiles) + methods = [:host_up, :host_down, :host_found, :host_lost, :setup, :teardown, + :distance, :plan] + options[:execution_profiles].each do |name, profile| + load_balancing_policy = profile.load_balancing_policy + unless load_balancing_policy.nil? + Util.assert_responds_to_all(methods, load_balancing_policy, + ":load_balancing_policy in execution profile #{name} #{load_balancing_policy.inspect} must respond " \ + "to #{methods.inspect}, but doesn't" + ) + end + end + end if options.key?(:reconnection_policy) reconnection_policy = options[:reconnection_policy] @@ -584,22 +621,46 @@ def self.validate_and_massage_options(options) if options.key?(:retry_policy) retry_policy = options[:retry_policy] + Util.assert(options[:execution_profiles].nil?, ":retry_policy cannot be specified when using execution profiles") methods = [:read_timeout, :write_timeout, :unavailable] - Util.assert_responds_to_all(methods, retry_policy) do ":retry_policy #{retry_policy.inspect} must respond to #{methods.inspect}, " \ "but doesn't" end end + if options.key?(:execution_profiles) + methods = [:read_timeout, :write_timeout, :unavailable] + options[:execution_profiles].each do |name, profile| + retry_policy = profile.retry_policy + unless retry_policy.nil? + Util.assert_responds_to_all(methods, retry_policy, + ":retry_policy in execution profile #{name} #{retry_policy.inspect} must " \ + "respond to #{methods.inspect}, but doesn't" + ) + end + end + end options[:listeners] = Array(options[:listeners]) if options.key?(:listeners) if options.key?(:consistency) consistency = options[:consistency] + Util.assert(options[:execution_profiles].nil?, ":consistency cannot be specified when using execution profiles") - Util.assert_one_of(CONSISTENCIES, consistency) do + Util.assert_one_of(CONSISTENCIES, consistency, ":consistency must be one of #{CONSISTENCIES.inspect}, " \ "#{consistency.inspect} given" + ) + end + if options.key?(:execution_profiles) + options[:execution_profiles].each do |name, profile| + consistency = profile.consistency + unless consistency.nil? + Util.assert_one_of(CONSISTENCIES, consistency, + ":consistency in execution profile #{name} must be one of #{CONSISTENCIES.inspect}, " \ + "#{consistency.inspect} given" + ) + end end end @@ -824,6 +885,8 @@ def self.validate_and_massage_options(options) require 'cassandra/execution/info' require 'cassandra/execution/options' +require 'cassandra/execution/profile' +require 'cassandra/execution/profile_manager' require 'cassandra/execution/trace' require 'cassandra/load_balancing' @@ -846,7 +909,9 @@ module Cassandra # @private VOID_STATEMENT = Statements::Void.new # @private - VOID_OPTIONS = Execution::Options.new(consistency: :one) + VOID_OPTIONS = Execution::Options.new(consistency: :one, + load_balancing_policy: LoadBalancing::Policies::RoundRobin.new, + retry_policy: Retry::Policies::Default.new) # @private NO_HOSTS = Errors::NoHostsAvailable.new end diff --git a/lib/cassandra/cluster.rb b/lib/cassandra/cluster.rb index 0868f1b41..78f4755d1 100644 --- a/lib/cassandra/cluster.rb +++ b/lib/cassandra/cluster.rb @@ -36,9 +36,8 @@ def initialize(logger, cluster_metadata, execution_options, connection_options, - load_balancing_policy, + profile_manager, reconnection_policy, - retry_policy, address_resolution_policy, connector, futures_factory, @@ -52,9 +51,8 @@ def initialize(logger, @metadata = cluster_metadata @execution_options = execution_options @connection_options = connection_options - @load_balancing_policy = load_balancing_policy + @profile_manager = profile_manager @reconnection_policy = reconnection_policy - @retry_policy = retry_policy @address_resolver = address_resolution_policy @connector = connector @futures = futures_factory @@ -62,7 +60,7 @@ def initialize(logger, @control_connection.on_close do |_cause| begin - @load_balancing_policy.teardown(self) + @profile_manager.teardown(self) rescue nil end @@ -201,14 +199,13 @@ def connect_async(keyspace = nil) @schema, @io_reactor, @connector, - @load_balancing_policy, + @profile_manager, @reconnection_policy, - @retry_policy, @address_resolver, @connection_options, @futures, @timestamp_generator) - session = Session.new(client, @execution_options, @futures) + session = Session.new(client, @execution_options, @futures, @profile_manager) promise = @futures.promise client.connect.on_complete do |f| @@ -282,9 +279,7 @@ def inspect "name=#{name.inspect}, " \ "port=#{@connection_options.port}, " \ "protocol_version=#{@connection_options.protocol_version}, " \ - "load_balancing_policy=#{@load_balancing_policy.inspect}, " \ - "consistency=#{@execution_options.consistency.inspect}, " \ - "timeout=#{@execution_options.timeout.inspect}, " \ + "profile_manager=#{@profile_manager.inspect}, " \ "hosts=#{hosts.inspect}, " \ "keyspaces=#{keyspaces.inspect}>" end diff --git a/lib/cassandra/cluster/client.rb b/lib/cassandra/cluster/client.rb index a0cc26645..77902e271 100644 --- a/lib/cassandra/cluster/client.rb +++ b/lib/cassandra/cluster/client.rb @@ -29,9 +29,8 @@ def initialize(logger, cluster_schema, io_reactor, connector, - load_balancing_policy, + profile_manager, reconnection_policy, - retry_policy, address_resolution_policy, connection_options, futures_factory, @@ -41,9 +40,8 @@ def initialize(logger, @schema = cluster_schema @reactor = io_reactor @connector = connector - @load_balancing_policy = load_balancing_policy + @profile_manager = profile_manager @reconnection_policy = reconnection_policy - @retry_policy = retry_policy @address_resolver = address_resolution_policy @connection_options = connection_options @futures = futures_factory @@ -67,7 +65,7 @@ def connect @state = :connecting @registry.each_host do |host| - distance = @load_balancing_policy.distance(host) + distance = @profile_manager.distance(host) case distance when :ignore @@ -168,7 +166,7 @@ def host_up(host) pool_size = 0 synchronize do - distance = @load_balancing_policy.distance(host) + distance = @profile_manager.distance(host) case distance when :ignore return Ione::Future.resolved @@ -248,7 +246,7 @@ def query(statement, options) promise = @futures.promise keyspace = @keyspace - plan = @load_balancing_policy.plan(keyspace, statement, options) + plan = options.load_balancing_policy.plan(keyspace, statement, options) send_request_by_plan(promise, keyspace, @@ -270,7 +268,7 @@ def prepare(cql, options) keyspace = @keyspace statement = VOID_STATEMENT - plan = @load_balancing_policy.plan(keyspace, statement, options) + plan = options.load_balancing_policy.plan(keyspace, statement, options) send_request_by_plan(promise, keyspace, @@ -303,7 +301,7 @@ def execute(statement, options) promise = @futures.promise keyspace = @keyspace - plan = @load_balancing_policy.plan(keyspace, statement, options) + plan = options.load_balancing_policy.plan(keyspace, statement, options) execute_by_plan(promise, keyspace, statement, options, request, plan, timeout) @@ -329,7 +327,7 @@ def batch(statement, options) timestamp, payload) keyspace = @keyspace - plan = @load_balancing_policy.plan(keyspace, statement, options) + plan = options.load_balancing_policy.plan(keyspace, statement, options) promise = @futures.promise batch_by_plan(promise, keyspace, statement, options, request, plan, timeout) @@ -1075,20 +1073,20 @@ def handle_response(response_future, case r when Protocol::UnavailableErrorResponse - decision = @retry_policy.unavailable(statement, + decision = options.retry_policy.unavailable(statement, r.consistency, r.required, r.alive, retries) when Protocol::WriteTimeoutErrorResponse - decision = @retry_policy.write_timeout(statement, + decision = options.retry_policy.write_timeout(statement, r.consistency, r.write_type, r.blockfor, r.received, retries) when Protocol::ReadTimeoutErrorResponse - decision = @retry_policy.read_timeout(statement, + decision = options.retry_policy.read_timeout(statement, r.consistency, r.blockfor, r.received, @@ -1445,7 +1443,7 @@ def wait_for_schema_agreement(connection, schedule) unless local.empty? host = @registry.host(connection.host) - if host && @load_balancing_policy.distance(host) != :ignore + if host && @profile_manager.distance(host) != :ignore versions << version = local.first['schema_version'] @logger.debug("Host #{host.ip} schema version is #{version}") end @@ -1453,7 +1451,7 @@ def wait_for_schema_agreement(connection, schedule) peers.each do |row| host = @registry.host(peer_ip(row)) - next unless host && @load_balancing_policy.distance(host) != :ignore + next unless host && @profile_manager.distance(host) != :ignore versions << version = row['schema_version'] @logger.debug("Host #{host.ip} schema version is #{version}") diff --git a/lib/cassandra/driver.rb b/lib/cassandra/driver.rb index 509f27498..186974454 100644 --- a/lib/cassandra/driver.rb +++ b/lib/cassandra/driver.rb @@ -84,7 +84,7 @@ def self.let(name, &block) cluster_registry, cluster_schema, cluster_metadata, - load_balancing_policy, + profile_manager.default_profile.load_balancing_policy, reconnection_policy, address_resolution_policy, connector, @@ -104,21 +104,22 @@ def self.let(name, &block) cluster_metadata, execution_options, connection_options, - load_balancing_policy, + profile_manager, reconnection_policy, - retry_policy, address_resolution_policy, connector, futures_factory, - timestamp_generator) + timestamp_generator) end let(:execution_options) do - Execution::Options.new(consistency: consistency, + Execution::Options.new(consistency: profile_manager.default_profile.consistency, trace: trace, page_size: page_size, - timeout: timeout, - idempotent: false) + timeout: profile_manager.default_profile.timeout, + idempotent: false, + load_balancing_policy: profile_manager.default_profile.load_balancing_policy, + retry_policy: profile_manager.default_profile.retry_policy) end let(:connection_options) do @@ -180,7 +181,18 @@ def self.let(name, &block) let(:connections_per_local_node) { nil } let(:connections_per_remote_node) { nil } let(:requests_per_connection) { nil } - + let(:default_execution_profile) { + Cassandra::Execution::Profile.new(load_balancing_policy: load_balancing_policy, + retry_policy: retry_policy, + consistency: consistency, + timeout: timeout) + } + let(:execution_profiles) { {} } + let(:profile_manager) { + Cassandra::Execution::ProfileManager.new( + {Cassandra::DEFAULT_EXECUTION_PROFILE => default_execution_profile}.merge(execution_profiles) + ) + } let(:listeners) { [] } def initialize(defaults = {}) @@ -188,8 +200,8 @@ def initialize(defaults = {}) end def connect(addresses) - load_balancing_policy.setup(cluster) - cluster_registry.add_listener(load_balancing_policy) + profile_manager.setup(cluster) + cluster_registry.add_listener(profile_manager) cluster_registry.add_listener(control_connection) listeners.each do |listener| cluster.register(listener) diff --git a/lib/cassandra/execution/options.rb b/lib/cassandra/execution/options.rb index cea5060b3..81a4a9ca7 100644 --- a/lib/cassandra/execution/options.rb +++ b/lib/cassandra/execution/options.rb @@ -64,21 +64,30 @@ class Options # }) attr_reader :payload + # @return [Cassandra::LoadBalancing::Policy] load-balancing policy that determines which node will run the next statement. + attr_reader :load_balancing_policy + + # @return [Cassandra::Retry::Policy] retry policy that determines how request retries should be handled for different + # failure modes. + attr_reader :retry_policy + # @private # @param options [Hash] execution options to validate and encapsulate # @param trusted_options [Options] (optional) base Execution::Options from which # to create this new Options object. def initialize(options, trusted_options = nil) - consistency = options[:consistency] - page_size = options[:page_size] - trace = options[:trace] - timeout = options[:timeout] - serial_consistency = options[:serial_consistency] - paging_state = options[:paging_state] - arguments = options[:arguments] - type_hints = options[:type_hints] - idempotent = options[:idempotent] - payload = options[:payload] + consistency = options[:consistency] + page_size = options[:page_size] + trace = options[:trace] + timeout = options[:timeout] + serial_consistency = options[:serial_consistency] + paging_state = options[:paging_state] + arguments = options[:arguments] + type_hints = options[:type_hints] + idempotent = options[:idempotent] + payload = options[:payload] + load_balancing_policy = options[:load_balancing_policy] + retry_policy = options[:retry_policy] # consistency is a required attribute of an Options object. If we are creating # an Options object from scratch (e.g. no trusted_options as base) validate the @@ -91,6 +100,24 @@ def initialize(options, trusted_options = nil) end end + # load_balancing_policy and retry_policy are required, but can fallback to trusted_options, just like + # consistency. + if trusted_options.nil? || !load_balancing_policy.nil? + methods = [:host_up, :host_down, :host_found, :host_lost, :setup, :teardown, + :distance, :plan] + Util.assert_responds_to_all(methods, load_balancing_policy, + ":load_balancing_policy #{load_balancing_policy.inspect} must respond " \ + "to #{methods.inspect}, but doesn't" + ) + end + if trusted_options.nil? || !retry_policy.nil? + methods = [:read_timeout, :write_timeout, :unavailable] + Util.assert_responds_to_all(methods, retry_policy, + ":retry_policy #{retry_policy.inspect} must respond to #{methods.inspect}, " \ + "but doesn't" + ) + end + unless serial_consistency.nil? Util.assert_one_of(SERIAL_CONSISTENCIES, serial_consistency) do ":serial_consistency must be one of #{SERIAL_CONSISTENCIES.inspect}, " \ @@ -173,6 +200,8 @@ def initialize(options, trusted_options = nil) @serial_consistency = serial_consistency @arguments = arguments @type_hints = type_hints + @load_balancing_policy = load_balancing_policy + @retry_policy = retry_policy else @consistency = consistency || trusted_options.consistency @page_size = page_size || trusted_options.page_size @@ -181,6 +210,8 @@ def initialize(options, trusted_options = nil) @serial_consistency = serial_consistency || trusted_options.serial_consistency @arguments = arguments || trusted_options.arguments @type_hints = type_hints || trusted_options.type_hints + @load_balancing_policy = load_balancing_policy || trusted_options.load_balancing_policy + @retry_policy = retry_policy || trusted_options.retry_policy end # The following fields are *not* inherited from trusted_options, so we always @@ -210,18 +241,24 @@ def eql?(other) other.serial_consistency == @serial_consistency && other.paging_state == @paging_state && other.arguments == @arguments && - other.type_hints == @type_hints + other.type_hints == @type_hints && + other.load_balancing_policy == @load_balancing_policy && + other.retry_policy == @retry_policy end alias == eql? # @private def override(*options) merged = options.unshift({}).inject do |base, opts| + # Skip nil args next base unless opts - Util.assert_instance_of(::Hash, opts) do - "options must be a Hash, #{options.inspect} given" + + if opts.is_a?(Cassandra::Execution::Profile) + base.merge!(opts.to_h) + else + Util.assert_instance_of(::Hash, opts, "options must be a Hash, #{opts.inspect} given") + base.merge!(opts) end - base.merge!(opts) end Options.new(merged, self) diff --git a/lib/cassandra/execution/profile.rb b/lib/cassandra/execution/profile.rb new file mode 100644 index 000000000..21e587eb8 --- /dev/null +++ b/lib/cassandra/execution/profile.rb @@ -0,0 +1,88 @@ +# encoding: utf-8 + +#-- +# Copyright 2013-2016 DataStax, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + +module Cassandra + module Execution + # A profile is a collection of settings to use when executing and preparing statements. Register different + # profiles when creating the {Cassandra::Cluster} and execute/prepare statements with a particular profile + # by providing its name to the relevant method in {Session}. + # + # @see Cassandra.cluster + # @see Session#execute_async + # @see Session#execute + # @see Session#prepare_async + # @see Session#prepare + class Profile + # @return [Cassandra::LoadBalancing::Policy] load-balancing policy that determines which node will run the next statement. + attr_reader :load_balancing_policy + + # @return [Cassandra::Retry::Policy] retry policy that determines how request retries should be handled for different + # failure modes. + attr_reader :retry_policy + + # @return [Symbol] consistency level with which to run statements. + attr_reader :consistency + + # @return [Numeric] request execution timeout in seconds. `nil` means there is no timeout. + attr_reader :timeout + + #@private + DEFAULT_OPTIONS={load_balancing_policy: nil, + retry_policy: nil, + consistency: nil, + timeout: 12} + + # @param options [Hash] hash of attributes. Unspecified attributes or attributes with nil values effectively + # fall back to the attributes in the default execution profile. + # @option options [Numeric] :timeout (12) Request execution timeout in + # seconds. Setting value to `nil` will remove request timeout. + # @option options [Cassandra::LoadBalancing::Policy] :load_balancing_policy (nil) Load-balancing policy that + # determines which node will run the next statement. + # @option options [Cassandra::Retry::Policy] :retry_policy (nil) Retry policy that determines how request + # retries should be handled for different failure modes. + # @option options [Symbol] :consistency (nil) Consistency level with which to run statements. Must be one + # of {Cassandra::CONSISTENCIES}. + def initialize(options = {}) + options = DEFAULT_OPTIONS.merge(options) + @load_balancing_policy = options[:load_balancing_policy] + @retry_policy = options[:retry_policy] + @consistency = options[:consistency] + @timeout = options[:timeout] + end + + # @private + def to_h + { + load_balancing_policy: @load_balancing_policy, + retry_policy: @retry_policy, + consistency: @consistency, + timeout: @timeout + } + end + + # @private + def inspect + "#<#{self.class.name}:0x#{object_id.to_s(16)} " \ + "load_balancing_policy=#{@load_balancing_policy.inspect}, " \ + "retry_policy=#{@retry_policy.inspect}, " \ + "consistency=#{@consistency.inspect}, " \ + "timeout=#{@timeout.inspect}>" + end + end + end +end diff --git a/lib/cassandra/execution/profile_manager.rb b/lib/cassandra/execution/profile_manager.rb new file mode 100644 index 000000000..65dbc3024 --- /dev/null +++ b/lib/cassandra/execution/profile_manager.rb @@ -0,0 +1,85 @@ +# encoding: utf-8 + +#-- +# Copyright 2013-2016 DataStax, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + +module Cassandra + module Execution + # @private + class ProfileManager + attr_reader :profiles + + def initialize(profiles) + @profiles = profiles + end + + def default_profile + @profiles[Cassandra::DEFAULT_EXECUTION_PROFILE] + end + + def setup(cluster) + lbp_broadcast(:setup, cluster) + end + + def teardown(cluster) + lbp_broadcast(:teardown, cluster) + end + + def distance(host) + # Return the min distance to the host, as per each lbp. + distances = Set.new + @profiles.each_value do |profile| + distances.add(profile.load_balancing_policy.distance(host)) if profile.load_balancing_policy + end + return :local if distances.include?(:local) + return :remote if distances.include?(:remote) + + # Fall back to ignore the host. + return :ignore + end + + def host_up(host) + lbp_broadcast(:host_up, host) + end + + def host_down(host) + lbp_broadcast(:host_down, host) + end + + def host_found(host) + lbp_broadcast(:host_found, host) + end + + def host_lost(host) + lbp_broadcast(:host_lost, host) + end + + # @private + def inspect + "#<#{self.class.name}:0x#{object_id.to_s(16)} " \ + "profiles=#{@profiles.inspect}>" + end + + private + + def lbp_broadcast(method, *args) + @profiles.each_value do |profile| + profile.load_balancing_policy.send(method, *args) if profile.load_balancing_policy + end + end + end + end +end diff --git a/lib/cassandra/execution/trace.rb b/lib/cassandra/execution/trace.rb index 84cc9d27a..949086f09 100644 --- a/lib/cassandra/execution/trace.rb +++ b/lib/cassandra/execution/trace.rb @@ -53,7 +53,7 @@ def ==(other) attr_reader :id # @private - def initialize(id, client) + def initialize(id, client, load_balancing_policy) @id = id @client = client @coordinator = nil @@ -65,6 +65,7 @@ def initialize(id, client) @client_ip = nil @loaded = false @loaded_events = false + @load_balancing_policy = load_balancing_policy mon_initialize end @@ -141,12 +142,12 @@ def load attempt = 1 data = @client.query(Statements::Simple.new(SELECT_SESSION % @id), - VOID_OPTIONS).get.first + VOID_OPTIONS.override(load_balancing_policy: @load_balancing_policy)).get.first while data.nil? && attempt <= 5 sleep(attempt * 0.4) data = @client.query(Statements::Simple.new(SELECT_SESSION % @id), - VOID_OPTIONS).get.first + VOID_OPTIONS.override(load_balancing_policy: @load_balancing_policy)).get.first break if data attempt += 1 end @@ -173,7 +174,7 @@ def load_events @events = [] @client.query(Statements::Simple.new(SELECT_EVENTS % @id), - VOID_OPTIONS).get.each do |row| + VOID_OPTIONS.override(load_balancing_policy: @load_balancing_policy)).get.each do |row| @events << Event.new(row['event_id'], row['activity'], row['source'], diff --git a/lib/cassandra/result.rb b/lib/cassandra/result.rb index 66fd926ba..1f124b58d 100644 --- a/lib/cassandra/result.rb +++ b/lib/cassandra/result.rb @@ -32,7 +32,7 @@ def execution_info @consistency, @retries, @trace_id ? - Execution::Trace.new(@trace_id, @client) : + Execution::Trace.new(@trace_id, @client, @options.load_balancing_policy) : nil) end diff --git a/lib/cassandra/session.rb b/lib/cassandra/session.rb index 4691a5653..fd666c30d 100644 --- a/lib/cassandra/session.rb +++ b/lib/cassandra/session.rb @@ -29,10 +29,11 @@ class Session def_delegators :@client, :keyspace # @private - def initialize(client, default_options, futures_factory) + def initialize(client, default_options, futures_factory, profile_manager) @client = client @options = default_options @futures = futures_factory + @profile_manager = profile_manager end # Executes a given statement and returns a future result @@ -66,6 +67,8 @@ def initialize(client, default_options, futures_factory) # statement can be retried safely on timeout. # @option options [Hash<[String, Symbol], String>] :payload (nil) custom # outgoing payload to be sent with the request. + # @option options [String, Symbol] :execution_profile (nil) name of {Cassandra::Execution::Profile} + # from which to obtain certain query options. Defaults to the cluster's default execution profile. # # @see Cassandra.cluster Options that can be specified on the cluster-level # and their default values. @@ -78,14 +81,9 @@ def initialize(client, default_options, futures_factory) # # @return [Cassandra::Future] # - # @see Cassandra::Session#execute A list of errors this future can be - # resolved with + # @see Cassandra::Session#execute A list of errors this future can be resolved with def execute_async(statement, options = nil) - options = if options - @options.override(options) - else - @options - end + options = merge_execution_options(options) case statement when ::String @@ -110,7 +108,7 @@ def execute_async(statement, options = nil) # Cassandra::Statements::Bound, Cassandra::Statements::Prepared] # statement to execute # - # @param options [Hash] (nil) a customizable set of options + # @param options [Hash] (nil) a customizable set of options. See {#execute_async} for details. # # @see Cassandra::Session#execute_async # @see Cassandra::Future#get @@ -138,15 +136,13 @@ def execute(statement, options = nil) # @option options [Boolean] :idempotent (false) specify whether the # statement being prepared can be retried safely on timeout during # execution. + # @option options [String, Symbol] :execution_profile (nil) name of {Cassandra::Execution::Profile} + # from which to obtain certain query options. Defaults to the cluster's default execution profile. # # @return [Cassandra::Future] future # prepared statement def prepare_async(statement, options = nil) - options = if options.is_a?(::Hash) - @options.override(options) - else - @options - end + options = merge_execution_options(options) case statement when ::String @@ -161,14 +157,20 @@ def prepare_async(statement, options = nil) end # A blocking wrapper around {Cassandra::Session#prepare_async} + # + # @param statement [String, Cassandra::Statements::Simple] a statement to + # prepare + # + # @param options [Hash] (nil) a customizable set of options. See {#prepare_async} for details. + # # @see Cassandra::Session#prepare_async # @see Cassandra::Future#get # # @return [Cassandra::Statements::Prepared] prepared statement # @raise [Cassandra::Errors::NoHostsAvailable] if none of the hosts can be reached # @raise [Cassandra::Errors::ExecutionError] if Cassandra returns an error response - def prepare(*args) - prepare_async(*args).get + def prepare(statement, options = nil) + prepare_async(statement, options).get end # Returns a logged {Statements::Batch} instance and optionally yields it to @@ -235,5 +237,32 @@ def inspect "@keyspace=#{keyspace.inspect}, " \ "@options=#{@options.inspect}>" end + + private + + # @private + def merge_execution_options(options) + if options + Util.assert_instance_of(::Hash, options, "options must be a Hash, #{options.inspect} given") + # Yell if the caller gave us a bad profile name. + execution_profile = nil + if options.key?(:execution_profile) + execution_profile = @profile_manager.profiles[options[:execution_profile]] + unless execution_profile + raise ::ArgumentError.new("Unknown execution profile #{options[:execution_profile]}") + end + end + + # This looks a little hokey, so let's explain: Execution::Options.override takes a + # varargs-style array of things to merge into the base options object (to produce + # a new Options object, not mutate the base). If an execution profile was specified, + # we want its attributes to override the base options. In addition, if individual options + # were specified, we want *those* to take precedence over the execution profile attributes. + # So we override in this order. + @options.override(execution_profile, options) + else + @options + end + end end end diff --git a/lib/cassandra/statements/prepared.rb b/lib/cassandra/statements/prepared.rb index 77d3e9210..b881d0bed 100644 --- a/lib/cassandra/statements/prepared.rb +++ b/lib/cassandra/statements/prepared.rb @@ -151,7 +151,7 @@ def execution_info @consistency, @retries, @trace_id ? - Execution::Trace.new(@trace_id, @client) : + Execution::Trace.new(@trace_id, @client, @options.load_balancing_policy) : nil) end diff --git a/spec/cassandra/cluster/client_spec.rb b/spec/cassandra/cluster/client_spec.rb index 4dec5d764..c8700af7d 100644 --- a/spec/cassandra/cluster/client_spec.rb +++ b/spec/cassandra/cluster/client_spec.rb @@ -40,7 +40,8 @@ class Cluster } } let(:driver) { Driver.new(driver_settings) } - let(:client) { Client.new(driver.logger, driver.cluster_registry, driver.cluster_schema, driver.io_reactor, driver.connector, driver.load_balancing_policy, driver.reconnection_policy, driver.retry_policy, driver.address_resolution_policy, driver.connection_options, driver.futures_factory, driver.timestamp_generator) } + let(:client) { Client.new(driver.logger, driver.cluster_registry, driver.cluster_schema, driver.io_reactor, driver.connector, driver.profile_manager, driver.reconnection_policy, driver.address_resolution_policy, driver.connection_options, driver.futures_factory, driver.timestamp_generator) } + let(:execution_options) { driver.execution_options } before do io_reactor.connection_options = driver.connection_options @@ -70,6 +71,12 @@ class Cluster expect(io_reactor).to have(4).connections end + it 'gets distance from profile-manager' do + driver.profile_manager.stub(:distance) { :remote } + client.connect.value + expect(io_reactor).to have(2).connections + end + it 'can be called multiple times' do future = client.connect expect(future).to eq(client.connect) @@ -263,6 +270,13 @@ class Cluster end end + it 'gets distance from policy_manager' do + driver.profile_manager.stub(:distance) { :remote } + expect do + client.host_up(Cassandra::Host.new('1.1.1.1')) + end.to change { io_reactor.connections.size }.from(4).to(5) + end + context 'host not responding' do let(:address) { '1.1.1.1' } let(:host) { Host.new(address) } @@ -375,7 +389,7 @@ class Cluster end end client.connect.value - client.query(Statements::Simple.new('SELECT * FROM songs'), Execution::Options.new(:consistency => :one)).get + client.query(Statements::Simple.new('SELECT * FROM songs'), execution_options).get expect(handled).to be_truthy end @@ -403,10 +417,10 @@ class Cluster end end client.connect.value - client.query(Statements::Simple.new('USE foo'), Execution::Options.new(:consistency => :one)).get + client.query(Statements::Simple.new('USE foo'), execution_options).get # make sure we get a different host in the load balancing plan cluster_registry.hosts.delete(cluster_registry.hosts.first) - client.query(Statements::Simple.new('SELECT * FROM songs'), Execution::Options.new(:consistency => :one)).get + client.query(Statements::Simple.new('SELECT * FROM songs'), execution_options).get expect(count).to eq(2) end @@ -434,10 +448,10 @@ class Cluster end end client.connect.value - client.query(Statements::Simple.new('USE "FooBar"'), Execution::Options.new(:consistency => :one)).get + client.query(Statements::Simple.new('USE "FooBar"'), execution_options).get # make sure we get a different host in the load balancing plan cluster_registry.hosts.delete(cluster_registry.hosts.first) - client.query(Statements::Simple.new('SELECT * FROM songs'), Execution::Options.new(:consistency => :one)).get + client.query(Statements::Simple.new('SELECT * FROM songs'), execution_options).get expect(count).to eq(2) end @@ -469,7 +483,7 @@ class Cluster end end client.connect.value - client.query(Statements::Simple.new('SELECT * FROM songs'), Execution::Options.new(:consistency => :one)).get + client.query(Statements::Simple.new('SELECT * FROM songs'), execution_options).get expect(attempts).to have(2).items expect(attempts).to eq(hosts) end @@ -496,7 +510,7 @@ class Cluster end client.connect.value future = client.query(Statements::Simple.new('SELECT * FROM songs'), - Execution::Options.new(:consistency => :one)) + execution_options) got_excp = false future.on_failure do |ex| got_excp = true @@ -535,7 +549,7 @@ class Cluster end client.connect.value client.query(Statements::Simple.new('SELECT * FROM songs', nil, nil, true), - Execution::Options.new(:consistency => :one)).get + execution_options).get expect(attempts).to have(2).items expect(attempts).to eq(hosts) end @@ -560,7 +574,7 @@ class Cluster end client.connect.value expect do - client.query(Statements::Simple.new('SELECT * FROM songs'), Execution::Options.new(:consistency => :one)).get + client.query(Statements::Simple.new('SELECT * FROM songs'), execution_options).get end.to raise_error(Errors::NoHostsAvailable) end @@ -585,7 +599,7 @@ class Cluster client.connect.value expect do - client.query(Statements::Simple.new('SELECT * FROM songs'), Execution::Options.new(:consistency => :one)).get + client.query(Statements::Simple.new('SELECT * FROM songs'), execution_options).get end.to raise_error(Cassandra::Errors::InvalidError, 'blargh') end @@ -618,14 +632,14 @@ class Cluster end end client.connect.value - client.query(Statements::Simple.new('USE foo'), Execution::Options.new(:consistency => :one)).get + client.query(Statements::Simple.new('USE foo'), execution_options).get # make sure we get a different host in the load balancing plan cluster_registry.remove_host(cluster_registry.hosts.first) completed = 0 5.times do - f = client.query(Statements::Simple.new('SELECT * FROM songs'), Execution::Options.new(:consistency => :one)) + f = client.query(Statements::Simple.new('SELECT * FROM songs'), execution_options) f.on_success do completed += 1 end @@ -652,7 +666,7 @@ class Cluster end end client.connect.value - statement = client.prepare('SELECT * FROM songs', Execution::Options.new(:consistency => :one)).get + statement = client.prepare('SELECT * FROM songs', execution_options).get expect(statement.cql).to eq('SELECT * FROM songs') end end @@ -676,8 +690,8 @@ class Cluster end end client.connect.value - statement = client.prepare('SELECT * FROM songs', Execution::Options.new(:consistency => :one)).get - client.execute(statement.bind, Execution::Options.new(:consistency => :one)).get + statement = client.prepare('SELECT * FROM songs', execution_options).get + client.execute(statement.bind, execution_options).get expect(sent).to be_truthy end @@ -699,12 +713,12 @@ class Cluster end end client.connect.value - statement = client.prepare('SELECT * FROM songs', Execution::Options.new(:consistency => :one)).get + statement = client.prepare('SELECT * FROM songs', execution_options).get # make sure we get a different host in the load balancing plan cluster_registry.hosts.delete(cluster_registry.hosts.first) - client.execute(statement.bind, Execution::Options.new(:consistency => :one)).get + client.execute(statement.bind, execution_options).get expect(count).to eq(2) end @@ -732,12 +746,12 @@ class Cluster end end client.connect.value - statement = client.prepare('SELECT * FROM songs', Execution::Options.new(:consistency => :one)).get + statement = client.prepare('SELECT * FROM songs', execution_options).get # make sure we get a different host in the load balancing plan cluster_registry.hosts.delete(statement.execution_info.hosts.first) - client.execute(statement.bind, Execution::Options.new(:consistency => :one)).get + client.execute(statement.bind, execution_options).get expect(count).to eq(3) expect(error).to be(false) end @@ -766,9 +780,9 @@ class Cluster end client.connect.value - statement = client.prepare('SELECT * FROM songs', Execution::Options.new(:consistency => :one)).get + statement = client.prepare('SELECT * FROM songs', execution_options).get - client.execute(statement.bind, Execution::Options.new(:consistency => :one)).get + client.execute(statement.bind, execution_options).get expect(attempts).to have(2).items expect(attempts.sort!).to eq(hosts) @@ -792,10 +806,10 @@ class Cluster client.connect.value - statement = client.prepare('SELECT * FROM songs', Execution::Options.new(:consistency => :one)).get + statement = client.prepare('SELECT * FROM songs', execution_options).get expect do - client.execute(statement.bind, Execution::Options.new(:consistency => :one)).get + client.execute(statement.bind, execution_options).get end.to raise_error(Cassandra::Errors::InvalidError, 'blargh') end @@ -818,9 +832,9 @@ class Cluster client.connect.value - statement = client.prepare('SELECT * FROM songs', Execution::Options.new(:consistency => :one)).get + statement = client.prepare('SELECT * FROM songs', execution_options).get - expect(client.execute(statement.bind, Execution::Options.new(:consistency => :one)).get) + expect(client.execute(statement.bind, execution_options).get) .to be_instance_of(Results::Void) end @@ -842,10 +856,10 @@ class Cluster client.connect.value - statement = client.prepare('SELECT * FROM songs', Execution::Options.new(:consistency => :one)).get + statement = client.prepare('SELECT * FROM songs', execution_options).get expect do - client.execute(statement.bind, Execution::Options.new(:consistency => :one)).get + client.execute(statement.bind, execution_options).get end.to raise_error(Errors::NoHostsAvailable) end end @@ -877,7 +891,7 @@ class Cluster allow(batch_request).to receive(:clear) expect(batch_request).to receive(:add_query).once.with('INSERT INTO songs (id, title, album, artist, tags) VALUES (?, ?, ?, ?, ?)', [1, 2, 3, 4, 5], [Cassandra::Types.bigint, Cassandra::Types.bigint, Cassandra::Types.bigint, Cassandra::Types.bigint, Cassandra::Types.bigint]) expect(batch_request).to receive(:retries=).once.with(0) - client.batch(batch, Execution::Options.new(:consistency => :one, :trace => false)).get + client.batch(batch, execution_options.override(consistency: :one)).get expect(sent).to be_truthy end @@ -910,7 +924,8 @@ class Cluster client.connect.value - statement = client.prepare('INSERT INTO songs (id, title, album, artist, tags) VALUES (?, ?, ?, ?, ?)', Execution::Options.new(:consistency => :one, :trace => false)).get + statement = client.prepare('INSERT INTO songs (id, title, album, artist, tags) VALUES (?, ?, ?, ?, ?)', + execution_options.override(consistency: :one)).get batch.add(statement, arguments: [Cassandra::Uuid.new(1), 'some title', 'some album', 'some artist', Set['cool', 'stuff']]) @@ -918,7 +933,7 @@ class Cluster allow(batch_request).to receive(:clear) expect(batch_request).to receive(:add_prepared).once.with(123, [Cassandra::Uuid.new(1), 'some title', 'some album', 'some artist', Set['cool', 'stuff']], params_metadata.map(&:last)) expect(batch_request).to receive(:retries=).once.with(0) - client.batch(batch, Execution::Options.new(:consistency => :one, :trace => false)).get + client.batch(batch, execution_options.override(consistency: :one)).get expect(sent).to be_truthy end @@ -953,7 +968,8 @@ class Cluster client.connect.value - statement = client.prepare('INSERT INTO songs (id, title, album, artist, tags) VALUES (?, ?, ?, ?, ?)', Execution::Options.new(:consistency => :one, :trace => false)).get + statement = client.prepare('INSERT INTO songs (id, title, album, artist, tags) VALUES (?, ?, ?, ?, ?)', + execution_options.override(consistency: :one)).get batch.add(statement, arguments: [Cassandra::Uuid.new(1), 'some title', 'some album', 'some artist', Set['cool', 'stuff']]) @@ -965,7 +981,7 @@ class Cluster # make sure we get a different host in the load balancing plan cluster_registry.hosts.delete(cluster_registry.hosts.first) - client.batch(batch, Execution::Options.new(:consistency => :one, :trace => false)).get + client.batch(batch, execution_options.override(consistency: :one)).get expect(sent).to be_truthy expect(count).to eq(2) end @@ -994,7 +1010,7 @@ class Cluster client.connect.value batch = Statements::Batch::Logged.new(driver.execution_options) - client.batch(batch, Execution::Options.new(:consistency => :one)).get + client.batch(batch, execution_options).get expect(attempts).to have(2).items expect(attempts).to eq(hosts) @@ -1019,7 +1035,7 @@ class Cluster batch = Statements::Batch::Logged.new(driver.execution_options) expect do - client.batch(batch, Execution::Options.new(:consistency => :one)).get + client.batch(batch, execution_options).get end.to raise_error(Errors::NoHostsAvailable) end end diff --git a/spec/cassandra/cluster_spec.rb b/spec/cassandra/cluster_spec.rb index 9d63920fc..1062dead1 100644 --- a/spec/cassandra/cluster_spec.rb +++ b/spec/cassandra/cluster_spec.rb @@ -33,7 +33,7 @@ module Cassandra }) } - let(:cluster) { Cluster.new(driver.logger, io_reactor, executor, control_connection, cluster_registry, driver.cluster_schema, driver.cluster_metadata, driver.execution_options, driver.connection_options, load_balancing_policy, driver.reconnection_policy, driver.retry_policy, driver.address_resolution_policy, driver.connector, driver.futures_factory, driver.timestamp_generator) } + let(:cluster) { Cluster.new(driver.logger, io_reactor, executor, control_connection, cluster_registry, driver.cluster_schema, driver.cluster_metadata, driver.execution_options, driver.connection_options, driver.profile_manager, driver.reconnection_policy, driver.address_resolution_policy, driver.connector, driver.futures_factory, driver.timestamp_generator) } before do io_reactor.connection_options = driver.connection_options diff --git a/spec/cassandra/execution/options_spec.rb b/spec/cassandra/execution/options_spec.rb index a8f98c304..a8fe8a034 100644 --- a/spec/cassandra/execution/options_spec.rb +++ b/spec/cassandra/execution/options_spec.rb @@ -21,7 +21,45 @@ module Cassandra module Execution describe(Options) do - let(:base_options) { Options.new(timeout: 10, consistency: :one) } + let(:load_balancing_policy) { double('lbp') } + let(:retry_policy) { double('retry_policy') } + let(:base_options) { + Options.new(timeout: 10, consistency: :one, load_balancing_policy: load_balancing_policy, + retry_policy: retry_policy) + } + before do + allow(load_balancing_policy).to receive(:host_up) + allow(load_balancing_policy).to receive(:host_down) + allow(load_balancing_policy).to receive(:host_found) + allow(load_balancing_policy).to receive(:host_lost) + allow(load_balancing_policy).to receive(:setup) + allow(load_balancing_policy).to receive(:teardown) + allow(load_balancing_policy).to receive(:distance) + allow(load_balancing_policy).to receive(:plan) + + allow(retry_policy).to receive(:read_timeout) + allow(retry_policy).to receive(:write_timeout) + allow(retry_policy).to receive(:unavailable) + end + + context :initialize do + it 'should yell if load-balancing policy is invalid' do + ['junk', nil].each do |val| + expect { + Options.new(load_balancing_policy: val, retry_policy: retry_policy, consistency: :one) + }.to raise_error(ArgumentError) + end + end + + it 'should yell if retry policy is invalid' do + ['junk', nil].each do |val| + expect { + Options.new(load_balancing_policy: load_balancing_policy, retry_policy: val, consistency: :one) + }.to raise_error(ArgumentError) + end + end + end + it 'should allow nil timeout to override base non-nil timeout option' do result = Options.new({timeout: nil}, base_options) expect(result.timeout).to be_nil @@ -36,6 +74,15 @@ module Execution result = Options.new({}, base_options) expect(result.timeout).to eq(10) end + + it 'should override with execution-profile and simple attribute' do + profile = Profile.new(timeout: 21, consistency: :quorum) + result = base_options.override(profile, timeout: 42) + expect(result.load_balancing_policy).to be(load_balancing_policy) + expect(result.retry_policy).to be(retry_policy) + expect(result.consistency).to be(:quorum) + expect(result.timeout).to eq(42) + end end end end diff --git a/spec/cassandra/execution/profile_manager_spec.rb b/spec/cassandra/execution/profile_manager_spec.rb new file mode 100644 index 000000000..424c3783d --- /dev/null +++ b/spec/cassandra/execution/profile_manager_spec.rb @@ -0,0 +1,88 @@ +# encoding: utf-8 + +#-- +# Copyright 2013-2016 DataStax, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + +require 'spec_helper' + +module Cassandra + module Execution + describe(ProfileManager) do + let(:cluster) { double('cluster') } + let(:host) { double('host') } + let(:registry) { FakeClusterRegistry.new(['127.0.0.1', '127.0.0.2']) } + let(:lbp1) { FakeLoadBalancingPolicy.new(registry) } + let(:lbp2) { FakeLoadBalancingPolicy.new(registry) } + let(:lbp3) { FakeLoadBalancingPolicy.new(registry) } + let(:profile1) { Profile.new(load_balancing_policy: lbp1) } + let(:profile2) { Profile.new(load_balancing_policy: lbp2) } + let(:profile3) { Profile.new(load_balancing_policy: lbp3) } + let(:profile4) { Profile.new } + let(:subject) { + ProfileManager.new(p1: profile1, p2: profile2, p3: profile3, p4: profile4) + } + + it 'should delegate :setup to underlying load-balancing policies' do + expect(lbp1).to receive(:setup).with(cluster) + expect(lbp2).to receive(:setup).with(cluster) + expect(lbp3).to receive(:setup).with(cluster) + subject.setup(cluster) + end + + it 'should delegate :teardown to underlying load-balancing policies' do + expect(lbp1).to receive(:teardown).with(cluster) + expect(lbp2).to receive(:teardown).with(cluster) + expect(lbp3).to receive(:teardown).with(cluster) + subject.teardown(cluster) + end + + [:host_up, :host_down, :host_found, :host_lost].each do |event| + it "should delegate :#{event} to underlying load-balancing policies" do + expect(lbp1).to receive(event).with(host) + expect(lbp2).to receive(event).with(host) + expect(lbp3).to receive(event).with(host) + subject.send(event, host) + end + end + + context :distance do + it 'should return :local if any lbp shows a :local distance' do + expect(lbp1).to receive(:distance).with(host).and_return(:local) + expect(lbp2).to receive(:distance).with(host).and_return(:remote) + expect(lbp3).to receive(:distance).with(host).and_return(:ignore) + + expect(subject.distance(host)).to be(:local) + end + + it 'should return :remote if any lbp shows a :remote distance and none show :local' do + expect(lbp1).to receive(:distance).with(host).and_return(:ignore) + expect(lbp2).to receive(:distance).with(host).and_return(:remote) + expect(lbp3).to receive(:distance).with(host).and_return(:ignore) + + expect(subject.distance(host)).to be(:remote) + end + + it 'should return :ignore if all lbps ignore the host' do + expect(lbp1).to receive(:distance).with(host).and_return(:ignore) + expect(lbp2).to receive(:distance).with(host).and_return(:ignore) + expect(lbp3).to receive(:distance).with(host).and_return(:ignore) + + expect(subject.distance(host)).to be(:ignore) + end + end + end + end +end diff --git a/spec/cassandra/execution/trace_spec.rb b/spec/cassandra/execution/trace_spec.rb index 75006d382..c0d85a91f 100644 --- a/spec/cassandra/execution/trace_spec.rb +++ b/spec/cassandra/execution/trace_spec.rb @@ -23,7 +23,20 @@ module Execution describe(Trace) do let(:id) { Uuid::Generator.new.now } let(:client) { double('client') } - let(:trace) { Trace.new(id, client) } + let(:load_balancing_policy) { double('lbp') } + let(:trace) { Trace.new(id, client, load_balancing_policy) } + let(:execution_options) { VOID_OPTIONS.override(load_balancing_policy: load_balancing_policy) } + + before do + allow(load_balancing_policy).to receive(:host_up) + allow(load_balancing_policy).to receive(:host_down) + allow(load_balancing_policy).to receive(:host_found) + allow(load_balancing_policy).to receive(:host_lost) + allow(load_balancing_policy).to receive(:setup) + allow(load_balancing_policy).to receive(:teardown) + allow(load_balancing_policy).to receive(:distance) + allow(load_balancing_policy).to receive(:plan) + end [:coordinator, :duration, :parameters, :request, :started_at, :client].each do |method| describe("##{method}") do @@ -41,12 +54,12 @@ module Execution let(:future_rows) { Future::Value.new(rows) } it "loads #{method} from system_traces.sessions" do - expect(client).to receive(:query).once.with(statement, VOID_OPTIONS).and_return(future_rows) + expect(client).to receive(:query).once.with(statement, execution_options).and_return(future_rows) expect(trace.__send__(method)).to eq(data[method.to_s]) end it "loads #{method} only once" do - expect(client).to receive(:query).once.with(statement, VOID_OPTIONS).and_return(future_rows) + expect(client).to receive(:query).once.with(statement, execution_options).and_return(future_rows) 10.times { expect(trace.__send__(method)).to eq(data[method.to_s]) } end end @@ -70,12 +83,12 @@ module Execution let(:future_rows) { Future::Value.new(rows) } it "loads events from system_traces.events" do - expect(client).to receive(:query).once.with(statement, VOID_OPTIONS).and_return(future_rows) + expect(client).to receive(:query).once.with(statement, execution_options).and_return(future_rows) expect(trace.events).to have(5).events end it "loads events only once" do - expect(client).to receive(:query).once.with(statement, VOID_OPTIONS).and_return(future_rows) + expect(client).to receive(:query).once.with(statement, execution_options).and_return(future_rows) 10.times { expect(trace.events).to have(5).events } end end diff --git a/spec/cassandra/load_balancing/policies/dc_aware_round_robin_spec.rb b/spec/cassandra/load_balancing/policies/dc_aware_round_robin_spec.rb index e6edca128..17053d0c4 100644 --- a/spec/cassandra/load_balancing/policies/dc_aware_round_robin_spec.rb +++ b/spec/cassandra/load_balancing/policies/dc_aware_round_robin_spec.rb @@ -190,7 +190,12 @@ module Policies let(:keyspace) { 'foo' } let(:statement) { VOID_STATEMENT } let(:consistency) { :one } - let(:options) { Execution::Options.new({:consistency => consistency}) } + let(:retry_policy) { double('retry_policy') } + let(:options) { + Execution::Options.new({ consistency: consistency, + load_balancing_policy: policy, + retry_policy: retry_policy}) + } let(:plan) { policy.plan(keyspace, statement, options) } @@ -212,6 +217,10 @@ module Policies policy.host_up(host) remote_hosts.each {|host| policy.host_up(host)} local_hosts.each {|host| policy.host_up(host)} + + allow(retry_policy).to receive(:read_timeout) + allow(retry_policy).to receive(:write_timeout) + allow(retry_policy).to receive(:unavailable) end it 'prioritizes hosts first' do diff --git a/spec/cassandra/session_spec.rb b/spec/cassandra/session_spec.rb index 5fca81917..d922ce3c1 100644 --- a/spec/cassandra/session_spec.rb +++ b/spec/cassandra/session_spec.rb @@ -20,12 +20,49 @@ module Cassandra describe(Session) do - let(:default_options) { {:consistency => :one, :timeout => 5, :trace => false} } + let(:load_balancing_policy) { double('load_balancing_policy')} + let(:retry_policy) { double('retry_policy')} + let(:default_options) { + { + consistency: :one, + timeout: 5, + trace: false, + load_balancing_policy: load_balancing_policy, + retry_policy: retry_policy + } + } + let(:profile_manager) { double('profile_manager') } + let(:profile) { Execution::Profile.new } let(:session_options) { Execution::Options.new(default_options) } let(:client) { double('cassandra-driver') } - let(:session) { Session.new(client, session_options, Future::Factory.new(Executors::SameThread.new)) } + let(:session) { + Session.new(client, session_options, Future::Factory.new(Executors::SameThread.new), profile_manager) + } + + before do + allow(load_balancing_policy).to receive(:host_up) + allow(load_balancing_policy).to receive(:host_down) + allow(load_balancing_policy).to receive(:host_found) + allow(load_balancing_policy).to receive(:host_lost) + allow(load_balancing_policy).to receive(:setup) + allow(load_balancing_policy).to receive(:teardown) + allow(load_balancing_policy).to receive(:distance) + allow(load_balancing_policy).to receive(:plan) + + allow(retry_policy).to receive(:read_timeout) + allow(retry_policy).to receive(:write_timeout) + allow(retry_policy).to receive(:unavailable) + + allow(profile_manager).to receive(:profiles).and_return({good_profile: profile}) + end describe('#execute_async') do + it 'should error out if a bad profile name is provided' do + expect { + session.execute_async('SELECT * FROM songs', execution_profile: :bad_profile).get + }.to raise_error(ArgumentError) + end + context 'cql string' do context 'without arguments' do let(:cql) { 'SELECT * FROM songs' } @@ -78,6 +115,15 @@ module Cassandra expect(statement).to receive(:accept).once.with(client, session_options.override(options)).and_return(promise) expect(session.execute_async(cql, options)).to eq(promise) end + + it 'should handle execution profile' do + promise = double('promise') + statement = double('simple statement') + + expect(Statements::Simple).to receive(:new).once.with(cql, EMPTY_LIST, EMPTY_LIST, false).and_return(statement) + expect(statement).to receive(:accept).once.with(client, session_options.override(profile)).and_return(promise) + expect(session.execute_async(cql, {execution_profile: :good_profile})).to eq(promise) + end end end @@ -92,7 +138,7 @@ module Cassandra bound_statement = double('bound statement') options = double('options') - expect(session_options).to receive(:override).once.with(arguments: [1, 2, 3, 4, 5]).and_return(options) + expect(session_options).to receive(:override).once.with(nil, arguments: [1, 2, 3, 4, 5]).and_return(options) allow(options).to receive(:arguments).and_return([1, 2, 3, 4, 5]) expect(statement).to receive(:bind).with([1, 2, 3, 4, 5]).and_return(bound_statement) expect(client).to receive(:execute).once.with(bound_statement, options).and_return(promise) @@ -131,6 +177,12 @@ module Cassandra describe('#prepare_async') do let(:cql) { 'SELECT * FROM songs' } + it 'should error out if a bad profile name is provided' do + expect { + session.prepare_async('SELECT * FROM songs', execution_profile: :bad_profile).get + }.to raise_error(ArgumentError) + end + context 'without options' do it 'prepares cql with the client' do promise = double('promise') @@ -147,7 +199,16 @@ module Cassandra promise = double('promise') opts = double('options') - expect(session_options).to receive(:override).once.with(options).and_return(opts) + expect(session_options).to receive(:override).once.with(nil, options).and_return(opts) + expect(client).to receive(:prepare).once.with(cql, opts).and_return(promise) + expect(session.prepare_async(cql, options)).to eq(promise) + end + + it 'should handle execution profile' do + promise = double('promise') + opts = double('options') + options = {execution_profile: :good_profile} + expect(session_options).to receive(:override).once.with(profile, options).and_return(opts) expect(client).to receive(:prepare).once.with(cql, opts).and_return(promise) expect(session.prepare_async(cql, options)).to eq(promise) end diff --git a/spec/cassandra_spec.rb b/spec/cassandra_spec.rb index ed5f2947b..e17642074 100644 --- a/spec/cassandra_spec.rb +++ b/spec/cassandra_spec.rb @@ -15,12 +15,64 @@ def validate(*args) end end +class GoodLBP + def host_up + end + + def host_down + end + + def host_found + end + + def host_lost + end + + def setup + end + + def teardown + end + + def distance + end + + def plan + end +end + describe Cassandra do context :validate_and_massage_options do + let(:profiles) { + { + p1: Cassandra::Execution::Profile.new(load_balancing_policy: lbp), + p2: Cassandra::Execution::Profile.new(timeout: 0.00005) + } + } + + let(:lbp) { GoodLBP.new } + it 'should ignore spurious options' do expect(C.validate(foo: 1, timeout: 5)).to eq({ timeout: 5 }) end + context 'for execution profiles' do + it 'should validate :execution_profiles is a hash' do + expect(C.validate(execution_profiles: profiles)).to eq({ execution_profiles: profiles }) + expect { C.validate(execution_profiles: []) }.to raise_error(ArgumentError) + end + + { timeout: 7, + retry_policy: Cassandra::Retry::Policies::Default.new, + load_balancing_policy: GoodLBP.new, + consistency: :one + }.each do |attr, val| + it "should validate that #{attr.inspect} and :execution_profiles are not both specified" do + expect { C.validate(execution_profiles: profiles, attr => val) }.to raise_error(ArgumentError) + end + end + end + context 'for username and password' do it 'should require both or none of username and password' do # None @@ -173,12 +225,26 @@ def decompress end it 'should validate :timeout' do - expect { C.validate(timeout: 'a') }.to raise_error(ArgumentError) - expect { C.validate(timeout: -1) }.to raise_error(ArgumentError) - expect { C.validate(timeout: 0) }.to raise_error(ArgumentError) - expect(C.validate(timeout: 0.5)).to eq({ timeout: 0.5 }) - expect(C.validate(timeout: 38)).to eq({ timeout: 38 }) - expect(C.validate(timeout: nil)).to eq({ timeout: nil }) + ['a', -1, 0].each do |val| + expect { C.validate(timeout: val) }.to raise_error(ArgumentError) + end + [0.5, 38, nil].each do |val| + expect(C.validate(timeout: val)).to eq({ timeout: val }) + end + end + + it 'should validate :timeout in a profile' do + ['a', -1, 0].each do |val| + profiles.merge!(bad_profile: Cassandra::Execution::Profile.new(timeout: val)) + expect { C.validate(execution_profiles: profiles) }.to raise_error(ArgumentError) + end + + profiles.delete(:bad_profile) + + [0.5, 38, nil].each do |val| + profiles.merge!(good_profile: Cassandra::Execution::Profile.new(timeout: val)) + expect(C.validate(execution_profiles: profiles)).to eq({ execution_profiles: profiles }) + end end it 'should validate :heartbeat_interval' do @@ -203,7 +269,7 @@ def decompress expect { C.validate(schema_refresh_delay: 'a') }.to raise_error(ArgumentError) expect { C.validate(schema_refresh_delay: -1) }.to raise_error(ArgumentError) expect { C.validate(schema_refresh_delay: 0) }.to raise_error(ArgumentError) - expect {C.validate(schema_refresh_delay: nil)}.to raise_error(ArgumentError) + expect { C.validate(schema_refresh_delay: nil) }.to raise_error(ArgumentError) expect(C.validate(schema_refresh_delay: 0.5)).to eq({ schema_refresh_delay: 0.5 }) expect(C.validate(schema_refresh_delay: 38)).to eq({ schema_refresh_delay: 38 }) end @@ -212,41 +278,28 @@ def decompress expect { C.validate(schema_refresh_timeout: 'a') }.to raise_error(ArgumentError) expect { C.validate(schema_refresh_timeout: -1) }.to raise_error(ArgumentError) expect { C.validate(schema_refresh_timeout: 0) }.to raise_error(ArgumentError) - expect {C.validate(schema_refresh_timeout: nil)}.to raise_error(ArgumentError) + expect { C.validate(schema_refresh_timeout: nil) }.to raise_error(ArgumentError) expect(C.validate(schema_refresh_timeout: 0.5)).to eq({ schema_refresh_timeout: 0.5 }) expect(C.validate(schema_refresh_timeout: 38)).to eq({ schema_refresh_timeout: 38 }) end it 'should validate :load_balancing_policy option' do - class GoodPolicy - def host_up - end - - def host_down - end - - def host_found - end - - def host_lost - end - - def setup - end + expect(C.validate(load_balancing_policy: lbp)).to eq({ load_balancing_policy: lbp }) + ['junk', nil].each do |val| + expect { C.validate(load_balancing_policy: val) }.to raise_error(ArgumentError) + end + end - def teardown - end + it 'should validate :load_balancing_policy option in a profile' do + profiles.merge!(good_profile: Cassandra::Execution::Profile.new(load_balancing_policy: lbp)) + expect(C.validate(execution_profiles: profiles)).to eq({ execution_profiles: profiles }) - def distance - end + # nil is actually allowed, so we don't error out for it. + profiles.merge!(good_profile: Cassandra::Execution::Profile.new(load_balancing_policy: nil)) + expect(C.validate(execution_profiles: profiles)).to eq({ execution_profiles: profiles }) - def plan - end - end - policy = GoodPolicy.new - expect(C.validate(load_balancing_policy: policy)).to eq({ load_balancing_policy: policy }) - expect { C.validate(load_balancing_policy: 'junk') }.to raise_error(ArgumentError) - expect { C.validate(load_balancing_policy: nil) }.to raise_error(ArgumentError) + profiles.merge!(bad_profile: Cassandra::Execution::Profile.new(load_balancing_policy: 'junk')) + expect { C.validate(execution_profiles: profiles) }.to raise_error(ArgumentError) end it 'should validate :reconnection_policy option' do @@ -277,6 +330,30 @@ def unavailable expect { C.validate(retry_policy: nil) }.to raise_error(ArgumentError) end + it 'should validate :retry_policy option in a profile' do + class GoodPolicy + def read_timeout + end + + def write_timeout + end + + def unavailable + end + end + policy = GoodPolicy.new + + profiles.merge!(good_profile: Cassandra::Execution::Profile.new(retry_policy: policy)) + expect(C.validate(execution_profiles: profiles)).to eq({ execution_profiles: profiles }) + + # nil is actually allowed, so we don't error out for it. + profiles.merge!(good_profile: Cassandra::Execution::Profile.new(retry_policy: nil)) + expect(C.validate(execution_profiles: profiles)).to eq({ execution_profiles: profiles }) + + profiles.merge!(bad_profile: Cassandra::Execution::Profile.new(retry_policy: 'junk')) + expect { C.validate(execution_profiles: profiles) }.to raise_error(ArgumentError) + end + it 'should massage :listeners into an array' do expect(C.validate(listeners: 'a')).to eq({ listeners: ['a'] }) expect(C.validate(listeners: ['a'])).to eq({ listeners: ['a'] }) @@ -290,22 +367,31 @@ def unavailable expect { C.validate(consistency: 'foo') }.to raise_error(ArgumentError) end + it 'should validate :consistency in a profile' do + Cassandra::CONSISTENCIES.each do |c| + profiles.merge!(good_profile: Cassandra::Execution::Profile.new(consistency: c)) + expect(C.validate(execution_profiles: profiles)).to eq({ execution_profiles: profiles }) + end + profiles.merge!(bad_profile: Cassandra::Execution::Profile.new(consistency: 'foo')) + expect { C.validate(execution_profiles: profiles) }.to raise_error(ArgumentError) + end + it 'should massage :nodelay to a boolean' do - expect(C.validate(nodelay: nil)).to eq({nodelay: false}) - expect(C.validate(nodelay: 1)).to eq({nodelay: true}) - expect(C.validate(nodelay: 0)).to eq({nodelay: true}) + expect(C.validate(nodelay: nil)).to eq({ nodelay: false }) + expect(C.validate(nodelay: 1)).to eq({ nodelay: true }) + expect(C.validate(nodelay: 0)).to eq({ nodelay: true }) end it 'should massage :trace to a boolean' do - expect(C.validate(trace: nil)).to eq({trace: false}) - expect(C.validate(trace: 1)).to eq({trace: true}) - expect(C.validate(trace: 0)).to eq({trace: true}) + expect(C.validate(trace: nil)).to eq({ trace: false }) + expect(C.validate(trace: 1)).to eq({ trace: true }) + expect(C.validate(trace: 0)).to eq({ trace: true }) end it 'should massage :shuffle_replicas to a boolean' do - expect(C.validate(shuffle_replicas: nil)).to eq({shuffle_replicas: false}) - expect(C.validate(shuffle_replicas: 1)).to eq({shuffle_replicas: true}) - expect(C.validate(shuffle_replicas: 0)).to eq({shuffle_replicas: true}) + expect(C.validate(shuffle_replicas: nil)).to eq({ shuffle_replicas: false }) + expect(C.validate(shuffle_replicas: 1)).to eq({ shuffle_replicas: true }) + expect(C.validate(shuffle_replicas: 0)).to eq({ shuffle_replicas: true }) end it 'should validate :page_size' do @@ -333,13 +419,13 @@ def unavailable class GoodFactory def error end - + def value end - + def promise end - + def all end end @@ -370,14 +456,14 @@ def resolve end it 'should massage :synchronize_schema to a boolean' do - expect(C.validate(synchronize_schema: nil)).to eq({synchronize_schema: false}) - expect(C.validate(synchronize_schema: 1)).to eq({synchronize_schema: true}) - expect(C.validate(synchronize_schema: 0)).to eq({synchronize_schema: true}) + expect(C.validate(synchronize_schema: nil)).to eq({ synchronize_schema: false }) + expect(C.validate(synchronize_schema: 1)).to eq({ synchronize_schema: true }) + expect(C.validate(synchronize_schema: 0)).to eq({ synchronize_schema: true }) end it 'should map :client_timestamps to a generator class or nil' do - expect(C.validate(client_timestamps: nil)).to eq({timestamp_generator: nil}) - expect(C.validate(client_timestamps: false)).to eq({timestamp_generator: nil}) + expect(C.validate(client_timestamps: nil)).to eq({ timestamp_generator: nil }) + expect(C.validate(client_timestamps: false)).to eq({ timestamp_generator: nil }) expect(C.validate({})).to eq({}) expected_class = RUBY_ENGINE == 'jruby' ? @@ -387,9 +473,11 @@ def resolve expect { C.validate(client_timestamps: Object.new) }.to raise_error(ArgumentError) valid_generator = Object.new + def valid_generator.next 42 end + expect(C.validate(client_timestamps: valid_generator)).to eq({ timestamp_generator: valid_generator }) end diff --git a/spec/regressions/RUBY-189_spec.rb b/spec/regressions/RUBY-189_spec.rb index 4915caf7e..decf98d92 100644 --- a/spec/regressions/RUBY-189_spec.rb +++ b/spec/regressions/RUBY-189_spec.rb @@ -42,7 +42,7 @@ class Cluster let(:driver) { Driver.new(driver_settings) } let(:client) { Client.new(driver.logger, driver.cluster_registry, driver.cluster_schema, driver.io_reactor, driver.connector, - driver.load_balancing_policy, driver.reconnection_policy, driver.retry_policy, + driver.profile_manager, driver.reconnection_policy, driver.address_resolution_policy, driver.connection_options, driver.futures_factory, driver.timestamp_generator) } diff --git a/spec/support/fake_cluster_registry.rb b/spec/support/fake_cluster_registry.rb index 0b73217b7..b604a20ad 100644 --- a/spec/support/fake_cluster_registry.rb +++ b/spec/support/fake_cluster_registry.rb @@ -71,6 +71,24 @@ def initialize(fake_cluster_registry) @index = -1 end + def host_up(*args) + end + + def host_down(*args) + end + + def host_found(*args) + end + + def host_lost(*args) + end + + def setup(*args) + end + + def teardown(*args) + end + def distance(host) @registry.hosts.include?(host) ? :local : :ignore end From 49a1fcf48ee2c75fba2b589be18519bcbf303268 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Fri, 23 Sep 2016 17:13:59 -0700 Subject: [PATCH 135/196] RUBY-261 - Support cdc option in table metadata. --- lib/cassandra/cluster/schema/fetchers.rb | 12 ++++++++---- lib/cassandra/column_container.rb | 10 ++++++++-- .../cluster/schema/fetchers/3.0.0-data.json | 1 + .../cluster/schema/fetchers/3.0.0-schema.cql | 1 + 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/lib/cassandra/cluster/schema/fetchers.rb b/lib/cassandra/cluster/schema/fetchers.rb index c49d60e74..096405dd5 100644 --- a/lib/cassandra/cluster/schema/fetchers.rb +++ b/lib/cassandra/cluster/schema/fetchers.rb @@ -572,7 +572,8 @@ def create_table_options(table_data, compaction_strategy, is_compact) compression_parameters, is_compact, table_data['crc_check_chance'], - table_data['extensions'] + table_data['extensions'], + nil ) end end @@ -741,7 +742,8 @@ def create_table_options(table_data, compaction_strategy, is_compact) compression_parameters, is_compact, table_data['crc_check_chance'], - table_data['extensions'] + table_data['extensions'], + nil ) end end @@ -815,7 +817,8 @@ def create_table_options(table_data, compaction_strategy, is_compact) compression_parameters, is_compact, table_data['crc_check_chance'], - table_data['extensions'] + table_data['extensions'], + nil ) end end @@ -1347,7 +1350,8 @@ def create_table_options(table_data, compaction_strategy, is_compact) compression, is_compact, table_data['crc_check_chance'], - table_data['extensions'] + table_data['extensions'], + table_data['cdc'] ) end diff --git a/lib/cassandra/column_container.rb b/lib/cassandra/column_container.rb index 6e79a393d..f81763957 100644 --- a/lib/cassandra/column_container.rb +++ b/lib/cassandra/column_container.rb @@ -60,6 +60,9 @@ class Options # @return [ColumnContainer::Compaction] the compaction strategy of this column-container. attr_reader :compaction_strategy + # @return whether or not change data capture is enabled on this table. + attr_reader :cdc + # @private # rubocop:disable Metrics/ParameterLists def initialize(comment, @@ -80,7 +83,8 @@ def initialize(comment, compression, compact_storage, crc_check_chance, - extensions) + extensions, + cdc) @comment = comment @read_repair_chance = read_repair_chance @local_read_repair_chance = local_read_repair_chance @@ -100,6 +104,7 @@ def initialize(comment, @compact_storage = compact_storage @crc_check_chance = crc_check_chance @extensions = extensions + @cdc = cdc end # Return whether to replicate counter updates to other replicas. It is *strongly* recommended @@ -130,6 +135,7 @@ def to_cql options << "bloom_filter_fp_chance = #{Util.encode_object(@bloom_filter_fp_chance)}" end options << "caching = #{Util.encode_object(@caching)}" unless @caching.nil? + options << "cdc = true" if @cdc options << "comment = #{Util.encode_object(@comment)}" unless @comment.nil? options << "compaction = #{@compaction_strategy.to_cql}" unless @compaction_strategy.nil? options << "compression = #{Util.encode_object(@compression)}" unless @compression.nil? @@ -153,7 +159,6 @@ def to_cql options << "read_repair_chance = #{Util.encode_object(@read_repair_chance)}" unless @read_repair_chance.nil? options << "replicate_on_write = '#{@replicate_on_write}'" unless @replicate_on_write.nil? options << "speculative_retry = #{Util.encode_object(@speculative_retry)}" unless @speculative_retry.nil? - options.join("\nAND ") end @@ -177,6 +182,7 @@ def eql?(other) @compact_storage == other.compact_storage? && @crc_check_chance == other.crc_check_chance && @extensions == other.extensions + @cdc == other.cdc end alias == eql? end diff --git a/spec/cassandra/cluster/schema/fetchers/3.0.0-data.json b/spec/cassandra/cluster/schema/fetchers/3.0.0-data.json index 8e1a13114..495b3cd2c 100644 --- a/spec/cassandra/cluster/schema/fetchers/3.0.0-data.json +++ b/spec/cassandra/cluster/schema/fetchers/3.0.0-data.json @@ -18,6 +18,7 @@ "keys": "ALL", "rows_per_partition": "NONE" }, + "cdc": true, "comment": "test table for unit tests", "compaction": { "class": "org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy", diff --git a/spec/cassandra/cluster/schema/fetchers/3.0.0-schema.cql b/spec/cassandra/cluster/schema/fetchers/3.0.0-schema.cql index 2e1d2f6ba..537510eea 100644 --- a/spec/cassandra/cluster/schema/fetchers/3.0.0-schema.cql +++ b/spec/cassandra/cluster/schema/fetchers/3.0.0-schema.cql @@ -8,6 +8,7 @@ CREATE TABLE simplex."t1" ( ) WITH bloom_filter_fp_chance = 0.01 AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} + AND cdc = true AND comment = 'test table for unit tests' AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} AND compression = {'chunk_length_in_kb': '64', 'class': 'LZ4Compressor'} From 4c6e38e86414a9bc420a648e8e8a98780baee872 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Sat, 24 Sep 2016 11:01:58 -0700 Subject: [PATCH 136/196] RUBY-256 - Execution profiles * Fixed broken spec. --- spec/cassandra/session_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/cassandra/session_spec.rb b/spec/cassandra/session_spec.rb index d922ce3c1..a8ab252b7 100644 --- a/spec/cassandra/session_spec.rb +++ b/spec/cassandra/session_spec.rb @@ -271,13 +271,13 @@ module Cassandra describe('#prepare') do let(:promise) { double('promise') } - let(:args) { [double('statement to prepare')] } + let(:statement) { double('statement to prepare') } it "resolves a promise returned by #prepare_async" do - expect(session).to receive(:prepare_async).with(*args).and_return(promise) + expect(session).to receive(:prepare_async).with(statement, nil).and_return(promise) expect(promise).to receive(:get).once - session.prepare(*args) + session.prepare(statement) end end From e2e8e9eeb3498f3c25e2ccb7f46895ffe410a041 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Sat, 24 Sep 2016 11:53:40 -0700 Subject: [PATCH 137/196] RUBY-256 - Execution profiles * Fixed broken cuke (race condition). --- features/basics/execution_profiles.feature | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/features/basics/execution_profiles.feature b/features/basics/execution_profiles.feature index ef6e62e2b..161377e30 100644 --- a/features/basics/execution_profiles.feature +++ b/features/basics/execution_profiles.feature @@ -16,15 +16,24 @@ Feature: Execution profiles p1: Cassandra::Execution::Profile.new(load_balancing_policy: WhiteList.new(['127.0.0.1'], RoundRobin.new)), p2: Cassandra::Execution::Profile.new(load_balancing_policy: WhiteList.new(['127.0.0.2'], RoundRobin.new)) } + cluster = Cassandra.cluster(execution_profiles: profiles) session = cluster.connect puts "Running with default profile" + + # By default, the driver uses a dc-aware, token-aware round-robin load balancing policy that + # is notified of which nodes are available in random order. To make this test's output + # deterministic, we sort the results by ip address. + ip_list = [] 3.times do rs = session.execute('select rpc_address from system.local') - puts rs.first['rpc_address'] + ip_list << rs.first['rpc_address'].to_s end + puts ip_list.sort.join("\n") + # p2 and p3 set up load-balancing policies that will match only one node, so there's no + # issue of hitting nodes in random order. puts "Running with profile p1" 3.times do rs = session.execute('select rpc_address from system.local', execution_profile: :p1) @@ -41,9 +50,9 @@ Feature: Execution profiles Then its output should contain: """ Running with default profile + 127.0.0.1 127.0.0.2 127.0.0.3 - 127.0.0.1 Running with profile p1 127.0.0.1 127.0.0.1 From a4cd54c27d5dbf15d828ff0a9e6e5d8488c6d047 Mon Sep 17 00:00:00 2001 From: Kishan Karunaratne Date: Mon, 26 Sep 2016 14:14:43 -0700 Subject: [PATCH 138/196] Remove C* 1.2 from CI tests --- build.yaml | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/build.yaml b/build.yaml index 75fe0edf8..5afc28062 100644 --- a/build.yaml +++ b/build.yaml @@ -4,7 +4,7 @@ schedules: matrix: exclude: - ruby: ['2.2', 'jruby9k'] - - cassandra: ['1.2', '2.0', '2.2', '3.0'] + - cassandra: ['2.0', '2.2', '3.0'] nightly: schedule: nightly branches: @@ -12,14 +12,6 @@ schedules: matrix: exclude: - ruby: jruby9k - - cassandra: 1.2 - weekly: - schedule: weekly - branches: - include: [master] - matrix: - exclude: - - ruby: jruby9k ruby: - 2.2 @@ -27,7 +19,6 @@ ruby: - jruby1.7 - jruby9k cassandra: - - 1.2 - 2.0 - 2.1 - 2.2 From f9e0513bb2393ab9499a8a3d3d43993303c1e92c Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Thu, 29 Sep 2016 14:13:17 -0700 Subject: [PATCH 139/196] RUBY-256 - Execution profiles * Allow primitive Cassandra.cluster(...) args along with execution_profiles. The simple args populate the default execution profile; don't allow the user to specify a default profile. * Make Cassandra::Execution::Profile objects fully formed, copying unset values from the default profile. * Don't make the profile-manager into an event listener; register the unique lbp's. This allows us to have multiple execution profiles with common lbp's. In particular, if you have shared lbp's and the profile-manager delegates to all of them, shared lbp's will get called multiple times for a single event. --- integration/execution_profiles_test.rb | 73 ------------------ lib/cassandra.rb | 9 --- lib/cassandra/driver.rb | 15 ++-- lib/cassandra/execution/profile.rb | 44 ++++++++++- lib/cassandra/execution/profile_manager.rb | 56 +++++--------- .../execution/profile_manager_spec.rb | 41 +++++----- spec/cassandra/execution/profile_spec.rb | 76 +++++++++++++++++++ spec/cassandra_spec.rb | 10 --- 8 files changed, 165 insertions(+), 159 deletions(-) delete mode 100644 integration/execution_profiles_test.rb create mode 100644 spec/cassandra/execution/profile_spec.rb diff --git a/integration/execution_profiles_test.rb b/integration/execution_profiles_test.rb deleted file mode 100644 index 15d0d86f6..000000000 --- a/integration/execution_profiles_test.rb +++ /dev/null @@ -1,73 +0,0 @@ -# encoding: utf-8 - -#-- -# Copyright 2013-2016 DataStax, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -#++ - -require File.dirname(__FILE__) + '/integration_test_case.rb' - -class ExecutionProfilesTest < IntegrationTestCase - def self.before_suite - @@ccm_cluster = CCM.setup_cluster(1, 2) - end - - def setup_schema - @@ccm_cluster.setup_schema(<<-CQL) - CREATE KEYSPACE simplex WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}; - USE simplex; - CREATE TABLE users (user_id BIGINT PRIMARY KEY, first VARCHAR, last VARCHAR, age BIGINT); - CREATE TABLE test (k text, v int, PRIMARY KEY (k, v)); - CQL - end - - def make_profile_without(attr) - profile_hash = { - consistency: :one, - load_balancing_policy: Cassandra::LoadBalancing::Policies::RoundRobin.new, - retry_policy: Cassandra::Retry::Policies::Default.new, - timeout: nil - } - profile_hash.delete(attr) - Cassandra::Execution::Profile.new(profile_hash) - end - - # Test that the default execution profile requires load-balancing policy, retry-policy, and consistency to be set. - # - # test_default_execution_profile_validation tests that when the default execution profile is overridden, - # load-balancing policy, retry-policy, and consistency are set. First, it creates a cluster with a legal - # override of the default profile. Then it attempts to create cluster objects with each of the required - # default-profile attributes missing and verifies that an ArgumentError occurs. - # - # @expected_errors [::ArgumentError] When a required attribute is missing in the default execution profile. - # - # @jira_ticket RUBY-256 - # @expected_result Successful cluster creation when all required profile attributes are set, errors otherwise. - # - # @test_assumptions A Cassandra cluster with at least one node. - # @test_category argument:validation - def test_default_execution_profile_validation - profiles = {Cassandra::DEFAULT_EXECUTION_PROFILE => make_profile_without(nil)} - cluster = Cassandra.cluster(execution_profiles: profiles) - cluster.close - - # Now try without a consistency, load_balancing_policy, retry_policy. - [:load_balancing_policy, :retry_policy, :consistency].each do |attr| - profiles = { Cassandra::DEFAULT_EXECUTION_PROFILE => make_profile_without(attr) } - assert_raises(ArgumentError, "removing #{attr} should raise") do - Cassandra.cluster(execution_profiles: profiles) - end - end - end -end diff --git a/lib/cassandra.rb b/lib/cassandra.rb index 49a211453..9a3ffe6f1 100644 --- a/lib/cassandra.rb +++ b/lib/cassandra.rb @@ -46,10 +46,6 @@ module Cassandra # @see Cassandra::Session#execute_async SERIAL_CONSISTENCIES = [:serial, :local_serial].freeze - # Name of the default execution profile. Use this constant as the key for an execution profile when initializing - # a {Cluster} to override the default execution profile with your own. - DEFAULT_EXECUTION_PROFILE = '__DEFAULT_EXECUTION_PROFILE__' - # A list of all possible write types that a # {Cassandra::Errors::WriteTimeoutError} can have. # @@ -521,7 +517,6 @@ def self.validate_and_massage_options(options) timeout = options[:timeout] unless timeout.nil? - Util.assert(options[:execution_profiles].nil?, ":timeout cannot be specified when using execution profiles") Util.assert_instance_of(::Numeric, timeout, ":timeout must be a number of seconds, #{timeout.inspect} given") Util.assert(timeout > 0, ":timeout must be greater than 0, #{timeout} given") end @@ -588,7 +583,6 @@ def self.validate_and_massage_options(options) if options.key?(:load_balancing_policy) load_balancing_policy = options[:load_balancing_policy] - Util.assert(options[:execution_profiles].nil?, ":load_balancing_policy cannot be specified when using execution profiles") methods = [:host_up, :host_down, :host_found, :host_lost, :setup, :teardown, :distance, :plan] Util.assert_responds_to_all(methods, load_balancing_policy) do @@ -621,7 +615,6 @@ def self.validate_and_massage_options(options) if options.key?(:retry_policy) retry_policy = options[:retry_policy] - Util.assert(options[:execution_profiles].nil?, ":retry_policy cannot be specified when using execution profiles") methods = [:read_timeout, :write_timeout, :unavailable] Util.assert_responds_to_all(methods, retry_policy) do ":retry_policy #{retry_policy.inspect} must respond to #{methods.inspect}, " \ @@ -645,8 +638,6 @@ def self.validate_and_massage_options(options) if options.key?(:consistency) consistency = options[:consistency] - Util.assert(options[:execution_profiles].nil?, ":consistency cannot be specified when using execution profiles") - Util.assert_one_of(CONSISTENCIES, consistency, ":consistency must be one of #{CONSISTENCIES.inspect}, " \ "#{consistency.inspect} given" diff --git a/lib/cassandra/driver.rb b/lib/cassandra/driver.rb index 186974454..81612c398 100644 --- a/lib/cassandra/driver.rb +++ b/lib/cassandra/driver.rb @@ -170,7 +170,7 @@ def self.let(name, &block) let(:page_size) { 10000 } let(:heartbeat_interval) { 30 } let(:idle_timeout) { 60 } - let(:timeout) { 12 } + let(:timeout) { Cassandra::Execution::Profile::DEFAULT_TIMEOUT } let(:synchronize_schema) { true } let(:schema_refresh_delay) { 1 } let(:schema_refresh_timeout) { 10 } @@ -188,11 +188,7 @@ def self.let(name, &block) timeout: timeout) } let(:execution_profiles) { {} } - let(:profile_manager) { - Cassandra::Execution::ProfileManager.new( - {Cassandra::DEFAULT_EXECUTION_PROFILE => default_execution_profile}.merge(execution_profiles) - ) - } + let(:profile_manager) { Cassandra::Execution::ProfileManager.new(default_execution_profile, execution_profiles) } let(:listeners) { [] } def initialize(defaults = {}) @@ -200,8 +196,11 @@ def initialize(defaults = {}) end def connect(addresses) - profile_manager.setup(cluster) - cluster_registry.add_listener(profile_manager) + profile_manager.load_balancing_policies.each do |lbp| + lbp.setup(cluster) + cluster_registry.add_listener(lbp) + end + cluster_registry.add_listener(control_connection) listeners.each do |listener| cluster.register(listener) diff --git a/lib/cassandra/execution/profile.rb b/lib/cassandra/execution/profile.rb index 21e587eb8..a55d034e3 100644 --- a/lib/cassandra/execution/profile.rb +++ b/lib/cassandra/execution/profile.rb @@ -41,11 +41,14 @@ class Profile # @return [Numeric] request execution timeout in seconds. `nil` means there is no timeout. attr_reader :timeout - #@private + # @private DEFAULT_OPTIONS={load_balancing_policy: nil, retry_policy: nil, consistency: nil, - timeout: 12} + timeout: :unspecified} + + # @private + DEFAULT_TIMEOUT = 12 # @param options [Hash] hash of attributes. Unspecified attributes or attributes with nil values effectively # fall back to the attributes in the default execution profile. @@ -65,16 +68,42 @@ def initialize(options = {}) @timeout = options[:timeout] end + def timeout + @timeout == :unspecified ? DEFAULT_TIMEOUT : @timeout + end + # @private def to_h { load_balancing_policy: @load_balancing_policy, retry_policy: @retry_policy, consistency: @consistency, - timeout: @timeout + timeout: timeout } end + # @private + def eql?(other) + other.is_a?(Profile) && \ + @load_balancing_policy == other.load_balancing_policy && \ + @retry_policy == other.retry_policy && \ + @consistency == other.consistency && \ + @timeout == other.instance_variable_get(:@timeout) + end + alias == eql? + + # @private + def hash + @hash ||= begin + h = 17 + h = 31 * h + @load_balancing_policy.hash + h = 31 * h + @retry_policy.hash + h = 31 * h + @consistency.hash + h = 31 * h + @timeout.hash + h + end + end + # @private def inspect "#<#{self.class.name}:0x#{object_id.to_s(16)} " \ @@ -83,6 +112,15 @@ def inspect "consistency=#{@consistency.inspect}, " \ "timeout=#{@timeout.inspect}>" end + + # @private + def merge_from(parent_profile) + @load_balancing_policy = parent_profile.load_balancing_policy if @load_balancing_policy.nil? + @retry_policy = parent_profile.retry_policy if @retry_policy.nil? + @consistency = parent_profile.consistency if @consistency.nil? + @timeout = parent_profile.timeout if @timeout == :unspecified + self + end end end end diff --git a/lib/cassandra/execution/profile_manager.rb b/lib/cassandra/execution/profile_manager.rb index 65dbc3024..b4437be3f 100644 --- a/lib/cassandra/execution/profile_manager.rb +++ b/lib/cassandra/execution/profile_manager.rb @@ -21,28 +21,36 @@ module Execution # @private class ProfileManager attr_reader :profiles + attr_reader :load_balancing_policies - def initialize(profiles) - @profiles = profiles - end + # Name of the default execution profile. Use this constant as the key for an execution profile when initializing + # a {Cluster} to override the default execution profile with your own. + DEFAULT_EXECUTION_PROFILE = '__DEFAULT_EXECUTION_PROFILE__' - def default_profile - @profiles[Cassandra::DEFAULT_EXECUTION_PROFILE] - end + def initialize(default_profile, profiles) + # Walk through the profiles and fill them out with attributes from the default profile when they're not + # set. Also, save off all of the load-balancing policies for easy access. + + @load_balancing_policies = Set.new + @load_balancing_policies << default_profile.load_balancing_policy if default_profile.load_balancing_policy + + profiles.each_value do |profile| + @load_balancing_policies << profile.load_balancing_policy if profile.load_balancing_policy + profile.merge_from(default_profile) + end - def setup(cluster) - lbp_broadcast(:setup, cluster) + @profiles = profiles.merge({DEFAULT_EXECUTION_PROFILE => default_profile}) end - def teardown(cluster) - lbp_broadcast(:teardown, cluster) + def default_profile + @profiles[DEFAULT_EXECUTION_PROFILE] end def distance(host) # Return the min distance to the host, as per each lbp. distances = Set.new - @profiles.each_value do |profile| - distances.add(profile.load_balancing_policy.distance(host)) if profile.load_balancing_policy + @load_balancing_policies.each do |lbp| + distances.add(lbp.distance(host)) end return :local if distances.include?(:local) return :remote if distances.include?(:remote) @@ -51,35 +59,11 @@ def distance(host) return :ignore end - def host_up(host) - lbp_broadcast(:host_up, host) - end - - def host_down(host) - lbp_broadcast(:host_down, host) - end - - def host_found(host) - lbp_broadcast(:host_found, host) - end - - def host_lost(host) - lbp_broadcast(:host_lost, host) - end - # @private def inspect "#<#{self.class.name}:0x#{object_id.to_s(16)} " \ "profiles=#{@profiles.inspect}>" end - - private - - def lbp_broadcast(method, *args) - @profiles.each_value do |profile| - profile.load_balancing_policy.send(method, *args) if profile.load_balancing_policy - end - end end end end diff --git a/spec/cassandra/execution/profile_manager_spec.rb b/spec/cassandra/execution/profile_manager_spec.rb index 424c3783d..c3d7f379a 100644 --- a/spec/cassandra/execution/profile_manager_spec.rb +++ b/spec/cassandra/execution/profile_manager_spec.rb @@ -27,35 +27,36 @@ module Execution let(:lbp1) { FakeLoadBalancingPolicy.new(registry) } let(:lbp2) { FakeLoadBalancingPolicy.new(registry) } let(:lbp3) { FakeLoadBalancingPolicy.new(registry) } + let(:retry_policy) { double('retry_policy') } let(:profile1) { Profile.new(load_balancing_policy: lbp1) } let(:profile2) { Profile.new(load_balancing_policy: lbp2) } let(:profile3) { Profile.new(load_balancing_policy: lbp3) } let(:profile4) { Profile.new } + let(:profile5) { Profile.new(load_balancing_policy: lbp1) } + let(:default_profile) { + Profile.new(load_balancing_policy: lbp1, retry_policy: retry_policy, consistency: :quorum) + } let(:subject) { - ProfileManager.new(p1: profile1, p2: profile2, p3: profile3, p4: profile4) + ProfileManager.new(default_profile, {p1: profile1, p2: profile2, p3: profile3, p4: profile4, p5: profile5}) } - it 'should delegate :setup to underlying load-balancing policies' do - expect(lbp1).to receive(:setup).with(cluster) - expect(lbp2).to receive(:setup).with(cluster) - expect(lbp3).to receive(:setup).with(cluster) - subject.setup(cluster) - end - - it 'should delegate :teardown to underlying load-balancing policies' do - expect(lbp1).to receive(:teardown).with(cluster) - expect(lbp2).to receive(:teardown).with(cluster) - expect(lbp3).to receive(:teardown).with(cluster) - subject.teardown(cluster) + it 'should fill out profiles with values from default profile' do + subject + expect(profile1).to eq(Profile.new(load_balancing_policy: lbp1, retry_policy: retry_policy, + consistency: :quorum, timeout: Profile::DEFAULT_TIMEOUT)) + expect(profile2).to eq(Profile.new(load_balancing_policy: lbp2, retry_policy: retry_policy, + consistency: :quorum, timeout: Profile::DEFAULT_TIMEOUT)) + expect(profile3).to eq(Profile.new(load_balancing_policy: lbp3, retry_policy: retry_policy, + consistency: :quorum, timeout: Profile::DEFAULT_TIMEOUT)) + expect(profile4).to eq(Profile.new(load_balancing_policy: lbp1, retry_policy: retry_policy, + consistency: :quorum, timeout: Profile::DEFAULT_TIMEOUT)) + expect(profile5).to eq(Profile.new(load_balancing_policy: lbp1, retry_policy: retry_policy, + consistency: :quorum, timeout: Profile::DEFAULT_TIMEOUT)) end - [:host_up, :host_down, :host_found, :host_lost].each do |event| - it "should delegate :#{event} to underlying load-balancing policies" do - expect(lbp1).to receive(event).with(host) - expect(lbp2).to receive(event).with(host) - expect(lbp3).to receive(event).with(host) - subject.send(event, host) - end + it 'should return unique list of lbps' do + lbps = Set.new([lbp1, lbp2, lbp3]) + expect(subject.load_balancing_policies).to eq(lbps) end context :distance do diff --git a/spec/cassandra/execution/profile_spec.rb b/spec/cassandra/execution/profile_spec.rb new file mode 100644 index 000000000..22c06f377 --- /dev/null +++ b/spec/cassandra/execution/profile_spec.rb @@ -0,0 +1,76 @@ +# encoding: utf-8 + +#-- +# Copyright 2013-2016 DataStax, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + +require 'spec_helper' + +module Cassandra + module Execution + describe(Profile) do + let(:profile1) { Profile.new(load_balancing_policy: lbp1) } + let(:profile2) { Profile.new(load_balancing_policy: lbp2) } + let(:profile3) { Profile.new(load_balancing_policy: lbp3) } + let(:profile4) { Profile.new } + + context :timeout do + it 'should return default timeout value if unspecified' do + expect(Profile.new.timeout).to eq(12) + end + + it 'should return nil if set with nil' do + expect(Profile.new(timeout: nil).timeout).to be_nil + end + + it 'should return timeout value if set with a real value' do + expect(Profile.new(timeout: 6).timeout).to eq(6) + end + end + + context :merge_from do + let(:lbp) { double('lbp') } + let(:lbp2) { double('lbp2') } + let(:retry_policy) { double('retry_policy') } + let(:retry_policy2) { double('retry_policy2') } + let(:default_profile) { Profile.new(load_balancing_policy: lbp, retry_policy: retry_policy, consistency: :one, + timeout: 10)} + let(:profile) { + Profile.new(load_balancing_policy: lbp2, retry_policy: retry_policy2, consistency: :quorum, timeout: 23) + } + let(:empty_profile) { Profile.new } + it 'should accept all attributes from parent profile if it has no attributes itself' do + expect(Profile.new.merge_from(default_profile)).to eq(default_profile) + end + + it 'should ignore all attributes from parent profile if it is fully specified itself' do + expect(profile.merge_from(default_profile)).to eq(profile) + end + + it 'should respect nil timeout in parent' do + parent_profile = Profile.new(timeout: nil) + expect(empty_profile.timeout).to eq(12) + expect(empty_profile.merge_from(parent_profile).timeout).to be_nil + end + + it 'should preserve its nil timeout when parent timeout is not nil' do + profile = Profile.new(timeout: nil) + expect(profile.timeout).to be_nil + expect(profile.merge_from(default_profile).timeout).to be_nil + end + end + end + end +end diff --git a/spec/cassandra_spec.rb b/spec/cassandra_spec.rb index e17642074..87c7c566b 100644 --- a/spec/cassandra_spec.rb +++ b/spec/cassandra_spec.rb @@ -61,16 +61,6 @@ def plan expect(C.validate(execution_profiles: profiles)).to eq({ execution_profiles: profiles }) expect { C.validate(execution_profiles: []) }.to raise_error(ArgumentError) end - - { timeout: 7, - retry_policy: Cassandra::Retry::Policies::Default.new, - load_balancing_policy: GoodLBP.new, - consistency: :one - }.each do |attr, val| - it "should validate that #{attr.inspect} and :execution_profiles are not both specified" do - expect { C.validate(execution_profiles: profiles, attr => val) }.to raise_error(ArgumentError) - end - end end context 'for username and password' do From c923308a8040bb3637b46f8e27783827ad753d5a Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Thu, 29 Sep 2016 17:44:32 -0700 Subject: [PATCH 140/196] RUBY-256 - Execution profiles * Updated CHANGELOG. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48591812a..a6ce7afb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ Features: * Do not mark a host as down if there are active connections. * Update Keyspace metadata to include collection of indexes defined in the keyspace. * Update Table metadata to include trigger-collection and view-collection metadata. +* Added execution profiles to encapsulate a group of request execution options. Bug Fixes: * [RUBY-255](https://datastax-oss.atlassian.net/browse/RUBY-255) ControlConnection.peer_ip ignores peers that are missing critical information in system.peers. From ed78bb02abd92339ae7e1a5b5a01c58c87dd95d9 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Fri, 30 Sep 2016 22:03:00 -0700 Subject: [PATCH 141/196] RUBY-254 - beta protocol support * Added allow_beta_protocol option to Cassandra.cluster * Added handling for v5 versions of read-failure and write-failure responses. * Added ability to run integration tests against a C* cluster using a locally build C* tree. --- CHANGELOG.md | 1 + integration/client_error_test.rb | 141 ++++++++++-------- lib/cassandra.rb | 15 +- lib/cassandra/cluster/connector.rb | 7 +- lib/cassandra/cluster/options.rb | 12 +- lib/cassandra/driver.rb | 4 +- lib/cassandra/errors.rb | 14 +- lib/cassandra/protocol.rb | 5 + lib/cassandra/protocol/cql_byte_buffer.rb | 21 +++ .../responses/read_failure_error_response.rb | 14 +- .../responses/write_failure_error_response.rb | 22 ++- lib/cassandra/protocol/v4.rb | 75 +++++++--- spec/cassandra/cluster/options_spec.rb | 41 +++-- spec/cassandra_spec.rb | 18 ++- support/ccm.rb | 13 +- 15 files changed, 275 insertions(+), 128 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6ce7afb2..07a7afa97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Features: * Update Keyspace metadata to include collection of indexes defined in the keyspace. * Update Table metadata to include trigger-collection and view-collection metadata. * Added execution profiles to encapsulate a group of request execution options. +* Added support for v5 beta protocol. Bug Fixes: * [RUBY-255](https://datastax-oss.atlassian.net/browse/RUBY-255) ControlConnection.peer_ip ignores peers that are missing critical information in system.peers. diff --git a/integration/client_error_test.rb b/integration/client_error_test.rb index 4d38ed068..140c40e83 100644 --- a/integration/client_error_test.rb +++ b/integration/client_error_test.rb @@ -69,32 +69,43 @@ def set_failing_nodes(failing_nodes, keyspace) # @test_category error_codes # def test_raise_error_on_write_failure - begin - skip("Client failure errors are only available in C* after 2.2") if CCM.cassandra_version < '2.2.0' - - cluster = Retry.with_attempts(5, Cassandra::Errors::NoHostsAvailable) { Cassandra.cluster } - session = cluster.connect + skip("Client failure errors are only available in C* after 2.2") if CCM.cassandra_version < '2.2.0' - session.execute("CREATE KEYSPACE testwritefail WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '3'}", - consistency: :all) rescue nil - session.execute("CREATE TABLE testwritefail.test (k int PRIMARY KEY, v int)", consistency: :all) + # Create a cluster object that tries to use the beta protocol. If it succeeds, the write failure response + # will have a map of instead of num-failures. When v5 is officially released, we + # can remove the allow_beta_protocol arg in this test. + cluster = Retry.with_attempts(5, Cassandra::Errors::NoHostsAvailable) { + Cassandra.cluster(allow_beta_protocol: true) + } + session = cluster.connect - # Disable one node - set_failing_nodes(["node1"], "testwritefail") + session.execute("CREATE KEYSPACE testwritefail WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '3'}", + consistency: :all) rescue nil + session.execute("CREATE TABLE IF NOT EXISTS testwritefail.test (k int PRIMARY KEY, v int)", consistency: :all) - # One node disabled should trigger a WriteFailure - assert_raises(Cassandra::Errors::WriteError) do - session.execute("INSERT INTO testwritefail.test (k, v) VALUES (1, 0)", consistency: :all) - end + # Disable one node + set_failing_nodes(["node1"], "testwritefail") - # Quorum should still work with two nodes - session.execute("INSERT INTO testwritefail.test (k, v) VALUES (1, 0)", consistency: :quorum) + # One node disabled should trigger a WriteFailure + ex = assert_raises(Cassandra::Errors::WriteError) do + session.execute("INSERT INTO testwritefail.test (k, v) VALUES (1, 0)", consistency: :all) + end - # Restart the node to clear jvm settings - set_failing_nodes([], "testwritefail") - ensure - cluster && cluster.close + # If we're speaking protocol version > 4, verify that we have a failure map in the exception. + connection_options = cluster.instance_variable_get(:@connection_options) + assert_operator(ex.failed, :>=, 1) + if connection_options.protocol_version > 4 + assert_equal(ex.failed, ex.failures_by_node.size) + assert_equal(0, ex.failures_by_node.values.first) end + + # Quorum should still work with two nodes + session.execute("INSERT INTO testwritefail.test (k, v) VALUES (1, 0)", consistency: :quorum) + + # Restart the node to clear jvm settings + set_failing_nodes([], "testwritefail") + ensure + cluster && cluster.close end # Test for validating ReadError @@ -117,33 +128,45 @@ def test_raise_error_on_write_failure def test_raise_error_on_read_failure skip("Client failure errors are only available in C* after 2.2") if CCM.cassandra_version < '2.2.0' - begin - cluster = Retry.with_attempts(5, Cassandra::Errors::NoHostsAvailable) { Cassandra.cluster } - session = cluster.connect - - session.execute("CREATE KEYSPACE testreadfail WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '3'}", - consistency: :all) rescue nil - session.execute("CREATE TABLE testreadfail.test2 (k int, v0 int, v1 int, PRIMARY KEY (k, v0))", consistency: :all) + # Create a cluster object that tries to use the beta protocol. If it succeeds, the write failure response + # will have a map of instead of num-failures. When v5 is officially released, we + # can remove the allow_beta_protocol arg in this test. + cluster = Retry.with_attempts(5, Cassandra::Errors::NoHostsAvailable) { + Cassandra.cluster(allow_beta_protocol: true) + } + session = cluster.connect + + session.execute("CREATE KEYSPACE testreadfail WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '3'}", + consistency: :all) rescue nil + session.execute("CREATE TABLE IF NOT EXISTS testreadfail.test2 (k int, v0 int, v1 int, PRIMARY KEY (k, v0))", + consistency: :all) + + # Insert wide rows + insert = session.prepare("INSERT INTO testreadfail.test2 (k, v0, v1) VALUES (1, ?, 1)") + (0..3000).each do |num| + session.execute(insert, arguments: [num]) + end - # Insert wide rows - insert = session.prepare("INSERT INTO testreadfail.test2 (k, v0, v1) VALUES (1, ?, 1)") - (0..3000).each do |num| - session.execute(insert, arguments: [num]) - end + # Delete wide rows + delete = session.prepare("DELETE v1 FROM testreadfail.test2 WHERE k = 1 AND v0 =?") + (0..2001).each do |num| + session.execute(delete, arguments: [num]) + end - # Delete wide rows - delete = session.prepare("DELETE v1 FROM testreadfail.test2 WHERE k = 1 AND v0 =?") - (0..2001).each do |num| - session.execute(delete, arguments: [num]) - end + # Tombstones should trigger ReadFailure + ex = assert_raises(Cassandra::Errors::ReadError) do + session.execute("SELECT * FROM testreadfail.test2 WHERE k = 1") + end - # Tombstones should trigger ReadFailure - assert_raises(Cassandra::Errors::ReadError) do - session.execute("SELECT * FROM testreadfail.test2 WHERE k = 1") - end - ensure - cluster && cluster.close + # If we're speaking protocol version > 4, verify that we have a failure map in the exception. + connection_options = cluster.instance_variable_get(:@connection_options) + assert_operator(ex.failed, :>=, 1) + if connection_options.protocol_version > 4 + assert_equal(ex.failed, ex.failures_by_node.size) + assert_equal(1, ex.failures_by_node.values.first) end + ensure + cluster && cluster.close end # Test for validating FunctionCallError @@ -165,30 +188,28 @@ def test_raise_error_on_read_failure def test_raise_error_on_function_failure skip("Client failure errors are only available in C* after 2.2") if CCM.cassandra_version < '2.2.0' - begin - cluster = Retry.with_attempts(5, Cassandra::Errors::NoHostsAvailable) { Cassandra.cluster } - session = cluster.connect + cluster = Retry.with_attempts(5, Cassandra::Errors::NoHostsAvailable) { Cassandra.cluster } + session = cluster.connect - session.execute("CREATE KEYSPACE testfunctionfail WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '3'}", - consistency: :all) rescue nil - session.execute("CREATE TABLE testfunctionfail.d (k int PRIMARY KEY , d double)", consistency: :all) + session.execute("CREATE KEYSPACE testfunctionfail WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '3'}", + consistency: :all) rescue nil + session.execute("CREATE TABLE IF NOT EXISTS testfunctionfail.d (k int PRIMARY KEY , d double)", consistency: :all) - # Create a UDF that throws an exception - session.execute("CREATE FUNCTION testfunctionfail.test_failure(d double) + # Create a UDF that throws an exception + session.execute("CREATE FUNCTION IF NOT EXISTS testfunctionfail.test_failure(d double) RETURNS NULL ON NULL INPUT RETURNS double LANGUAGE java AS 'throw new RuntimeException(\"failure\");'", - consistency: :all) + consistency: :all) - # Insert value to use for function - session.execute("INSERT INTO testfunctionfail.d (k, d) VALUES (0, 5.12)") + # Insert value to use for function + session.execute("INSERT INTO testfunctionfail.d (k, d) VALUES (0, 5.12)") - # FunctionFailure should be triggered - assert_raises(Cassandra::Errors::FunctionCallError) do - session.execute("SELECT test_failure(d) FROM testfunctionfail.d WHERE k = 0") - end - ensure - cluster && cluster.close + # FunctionFailure should be triggered + assert_raises(Cassandra::Errors::FunctionCallError) do + session.execute("SELECT test_failure(d) FROM testfunctionfail.d WHERE k = 0") end + ensure + cluster && cluster.close end end diff --git a/lib/cassandra.rb b/lib/cassandra.rb index 9a3ffe6f1..d4f59d72c 100644 --- a/lib/cassandra.rb +++ b/lib/cassandra.rb @@ -56,6 +56,7 @@ module Cassandra CLUSTER_OPTIONS = [ :address_resolution, :address_resolution_policy, + :allow_beta_protocol, :auth_provider, :client_cert, :client_timestamps, @@ -196,6 +197,9 @@ module Cassandra # nodes. By default, this is auto-negotiated to the highest common protocol version # that all nodes in `:hosts` speak. # + # @option options [Boolean] :allow_beta_protocol (false) whether the driver should attempt to speak to nodes + # with a beta version of the newest protocol (which is still under development). USE WITH CAUTION! + # # @option options [Boolean, Cassandra::TimestampGenerator] :client_timestamps (false) whether the driver # should send timestamps for each executed statement and possibly which timestamp generator to use. Enabling this # setting helps mitigate Cassandra cluster clock skew because the timestamp of the client machine will be used. @@ -658,6 +662,7 @@ def self.validate_and_massage_options(options) options[:nodelay] = !!options[:nodelay] if options.key?(:nodelay) options[:trace] = !!options[:trace] if options.key?(:trace) options[:shuffle_replicas] = !!options[:shuffle_replicas] if options.key?(:shuffle_replicas) + options[:allow_beta_protocol] = !!options[:allow_beta_protocol] if options.key?(:allow_beta_protocol) if options.key?(:page_size) page_size = options[:page_size] @@ -675,12 +680,16 @@ def self.validate_and_massage_options(options) protocol_version = options[:protocol_version] unless protocol_version.nil? Util.assert_instance_of(::Integer, protocol_version) - Util.assert_one_of(1..4, protocol_version) do - ":protocol_version must be a positive integer, #{protocol_version.inspect} given" - end + Util.assert_one_of(1..Cassandra::Protocol::Versions::MAX_SUPPORTED_VERSION, protocol_version, + ':protocol_version must be a positive integer between 1 and ' \ + "#{Cassandra::Protocol::Versions::MAX_SUPPORTED_VERSION}, #{protocol_version.inspect} given" + ) end end + Util.assert(!(options[:allow_beta_protocol] && options[:protocol_version]), + 'only one of :allow_beta_protocol and :protocol_version may be specified, both given') + if options.key?(:futures_factory) futures_factory = options[:futures_factory] methods = [:error, :value, :promise, :all] diff --git a/lib/cassandra/cluster/connector.rb b/lib/cassandra/cluster/connector.rb index f8769e71a..dea54563f 100644 --- a/lib/cassandra/cluster/connector.rb +++ b/lib/cassandra/cluster/connector.rb @@ -122,11 +122,8 @@ def do_connect(host) ssl: @connection_options.ssl) do |connection| raise Errors::ClientError, 'Not connected, reactor stopped' unless connection - if @connection_options.nodelay? - connection.to_io.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) - else - connection.to_io.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 0) - end + connection.to_io.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, + @connection_options.nodelay? ? 1 : 0) Protocol::CqlProtocolHandler.new(connection, @reactor, diff --git a/lib/cassandra/cluster/options.rb b/lib/cassandra/cluster/options.rb index 892ddb753..64adc3d08 100644 --- a/lib/cassandra/cluster/options.rb +++ b/lib/cassandra/cluster/options.rb @@ -25,7 +25,7 @@ class Options attr_reader :auth_provider, :compressor, :connect_timeout, :credentials, :heartbeat_interval, :idle_timeout, :port, :schema_refresh_delay, :schema_refresh_timeout, :ssl, :custom_type_handlers - attr_boolean :protocol_negotiable, :synchronize_schema, :nodelay + attr_boolean :protocol_negotiable, :synchronize_schema, :nodelay, :allow_beta_protocol attr_accessor :protocol_version @@ -46,7 +46,8 @@ def initialize(logger, schema_refresh_timeout, nodelay, requests_per_connection, - custom_types) + custom_types, + allow_beta_protocol) @logger = logger @protocol_version = protocol_version @credentials = credentials @@ -61,6 +62,7 @@ def initialize(logger, @schema_refresh_delay = schema_refresh_delay @schema_refresh_timeout = schema_refresh_timeout @nodelay = nodelay + @allow_beta_protocol = allow_beta_protocol @custom_type_handlers = {} custom_types.each do |type_klass| @custom_type_handlers[type_klass.type] = type_klass @@ -71,12 +73,14 @@ def initialize(logger, @requests_per_connection = requests_per_connection # If @protocol_version is nil, it means we want the driver to negotiate the - # protocol starting with our known max (4). If @protocol_version is not nil, + # protocol starting with our known max. If @protocol_version is not nil, # it means the user wants us to use a particular version, so we should not # support negotiation. @protocol_negotiable = @protocol_version.nil? - @protocol_version ||= 4 + @protocol_version ||= allow_beta_protocol ? + Cassandra::Protocol::Versions::BETA_VERSION : + Cassandra::Protocol::Versions::MAX_SUPPORTED_VERSION end def compression diff --git a/lib/cassandra/driver.rb b/lib/cassandra/driver.rb index 81612c398..8e80192bd 100644 --- a/lib/cassandra/driver.rb +++ b/lib/cassandra/driver.rb @@ -141,13 +141,15 @@ def self.let(name, &block) schema_refresh_timeout, nodelay, requests_per_connection, - custom_types + custom_types, + allow_beta_protocol ) end let(:custom_types) { [] } let(:port) { 9042 } let(:protocol_version) { nil } + let(:allow_beta_protocol) { false } let(:connect_timeout) { 10 } let(:ssl) { false } let(:logger) { NullLogger.new } diff --git a/lib/cassandra/errors.rb b/lib/cassandra/errors.rb index 99d1c61b2..ffd8d4cc5 100644 --- a/lib/cassandra/errors.rb +++ b/lib/cassandra/errors.rb @@ -348,6 +348,9 @@ class WriteError < ::StandardError attr_reader :received # @return [Integer] the number of writes failed attr_reader :failed + # @return [Hash] map of . This is new in v5 and is nil in previous versions + # of the Casssandra protocol. + attr_reader :failures_by_node # @private def initialize(message, @@ -363,7 +366,8 @@ def initialize(message, consistency, required, failed, - received) + received, + failures_by_node) super(message, payload, warnings, @@ -378,6 +382,7 @@ def initialize(message, @required = required @failed = failed @received = received + @failures_by_node = failures_by_node end end @@ -400,6 +405,9 @@ class ReadError < ::StandardError attr_reader :received # @return [Integer] the number of reads failed attr_reader :failed + # @return [Hash] map of . This is new in v5 and is nil in previous versions + # of the Casssandra protocol. + attr_reader :failures_by_node # @private def initialize(message, @@ -415,7 +423,8 @@ def initialize(message, consistency, required, failed, - received) + received, + failures_by_node) super(message, payload, warnings, @@ -430,6 +439,7 @@ def initialize(message, @required = required @failed = failed @received = received + @failures_by_node = failures_by_node end def retrieved? diff --git a/lib/cassandra/protocol.rb b/lib/cassandra/protocol.rb index 05aa3c66b..d9969f764 100644 --- a/lib/cassandra/protocol.rb +++ b/lib/cassandra/protocol.rb @@ -49,6 +49,11 @@ module Constants SCHEMA_CHANGE_TARGET_FUNCTION = 'FUNCTION'.freeze SCHEMA_CHANGE_TARGET_AGGREGATE = 'AGGREGATE'.freeze end + + module Versions + BETA_VERSION = 5 + MAX_SUPPORTED_VERSION = 4 + end end end diff --git a/lib/cassandra/protocol/cql_byte_buffer.rb b/lib/cassandra/protocol/cql_byte_buffer.rb index b936f1b99..c0acd5f34 100644 --- a/lib/cassandra/protocol/cql_byte_buffer.rb +++ b/lib/cassandra/protocol/cql_byte_buffer.rb @@ -204,6 +204,15 @@ def read_inet e.backtrace end + def read_inet_addr + size = read_byte + IPAddr.new_ntoh(read(size)) + rescue RangeError => e + raise Errors::DecodingError, + "Not enough bytes available to decode an INET addr: #{e.message}", + e.backtrace + end + def read_consistency index = read_unsigned_short if index >= CONSISTENCIES.size || CONSISTENCIES[index].nil? @@ -232,6 +241,18 @@ def read_bytes_map map end + def read_reason_map + # reason_map is new in v5. Starts with an int indicating the number of key-value pairs, followed by + # the key-value pairs. Keys are inet's, values are short int's. + map = {} + map_size = read_int + map_size.times do + key = read_inet_addr + map[key] = read_short + end + map + end + def read_string_multimap map = {} map_size = read_unsigned_short diff --git a/lib/cassandra/protocol/responses/read_failure_error_response.rb b/lib/cassandra/protocol/responses/read_failure_error_response.rb index 4be79984c..417009bf4 100644 --- a/lib/cassandra/protocol/responses/read_failure_error_response.rb +++ b/lib/cassandra/protocol/responses/read_failure_error_response.rb @@ -19,7 +19,7 @@ module Cassandra module Protocol class ReadFailureErrorResponse < ErrorResponse - attr_reader :consistency, :received, :blockfor, :numfailures, :data_present + attr_reader :consistency, :received, :blockfor, :numfailures, :data_present, :failures_by_node def initialize(custom_payload, warnings, @@ -29,14 +29,19 @@ def initialize(custom_payload, received, blockfor, numfailures, - data_present) + data_present, + failures_by_node) super(custom_payload, warnings, code, message) @consistency = consistency @received = received @blockfor = blockfor - @numfailures = numfailures @data_present = data_present + @failures_by_node = failures_by_node + + # If failures_by_node is set, numfailures isn't, and v.v. Set @numfailures to the size of the failure-map + # if numfailures is nil. + @numfailures = numfailures || @failures_by_node.size end def to_error(keyspace, statement, options, hosts, consistency, retries) @@ -53,7 +58,8 @@ def to_error(keyspace, statement, options, hosts, consistency, retries) @consistency, @blockfor, @numfailures, - @received) + @received, + @failures_by_node) end def to_s diff --git a/lib/cassandra/protocol/responses/write_failure_error_response.rb b/lib/cassandra/protocol/responses/write_failure_error_response.rb index 3cecb1276..cca4b116a 100644 --- a/lib/cassandra/protocol/responses/write_failure_error_response.rb +++ b/lib/cassandra/protocol/responses/write_failure_error_response.rb @@ -19,7 +19,7 @@ module Cassandra module Protocol class WriteFailureErrorResponse < ErrorResponse - attr_reader :consistency, :received, :blockfor, :numfailures, :write_type + attr_reader :consistency, :received, :blockfor, :numfailures, :write_type, :failures_by_node def initialize(custom_payload, warnings, @@ -29,16 +29,21 @@ def initialize(custom_payload, received, blockfor, numfailures, - write_type) + write_type, + failures_by_node) super(custom_payload, warnings, code, message) write_type.downcase! - @consistency = consistency - @received = received - @blockfor = blockfor - @numfailures = numfailures - @write_type = write_type.to_sym + @consistency = consistency + @received = received + @blockfor = blockfor + @write_type = write_type.to_sym + @failures_by_node = failures_by_node + + # If failures_by_node is set, numfailures isn't, and v.v. Set @numfailures to the size of the failure-map + # if numfailures is nil. + @numfailures = numfailures || @failures_by_node.size end def to_error(keyspace, statement, options, hosts, consistency, retries) @@ -55,7 +60,8 @@ def to_error(keyspace, statement, options, hosts, consistency, retries) @consistency, @blockfor, @numfailures, - @received) + @received, + @failures_by_node) end def to_s diff --git a/lib/cassandra/protocol/v4.rb b/lib/cassandra/protocol/v4.rb index 4ff3802cf..3aa95cad6 100644 --- a/lib/cassandra/protocol/v4.rb +++ b/lib/cassandra/protocol/v4.rb @@ -23,7 +23,7 @@ module V4 class Encoder HEADER_FORMAT = 'c2ncN'.freeze - def initialize(compressor = nil, protocol_version = 4) + def initialize(compressor, protocol_version) @compressor = compressor @protocol_version = protocol_version end @@ -31,6 +31,7 @@ def initialize(compressor = nil, protocol_version = 4) def encode(buffer, request, stream_id) flags = 0 flags |= 0x02 if request.trace? + flags |= 0x10 if @protocol_version == Cassandra::Protocol::Versions::BETA_VERSION body = CqlByteBuffer.new @@ -259,15 +260,33 @@ def decode_response(opcode, buffer.read_int, (buffer.read_byte != 0)) when 0x1300 - ReadFailureErrorResponse.new(custom_payload, - warnings, - code, - message, - buffer.read_consistency, - buffer.read_int, - buffer.read_int, - buffer.read_int, - (buffer.read_byte != 0)) + cl = buffer.read_consistency + received = buffer.read_int + block_for = buffer.read_int + if protocol_version < 5 + ReadFailureErrorResponse.new(custom_payload, + warnings, + code, + message, + cl, + received, + block_for, + buffer.read_int, + (buffer.read_byte != 0), + nil) + else + failures_by_node = buffer.read_reason_map + ReadFailureErrorResponse.new(custom_payload, + warnings, + code, + message, + cl, + received, + block_for, + nil, + (buffer.read_byte != 0), + failures_by_node) + end when 0x1400 FunctionFailureErrorResponse.new(custom_payload, warnings, @@ -277,15 +296,33 @@ def decode_response(opcode, buffer.read_string, buffer.read_string_list) when 0x1500 - WriteFailureErrorResponse.new(custom_payload, - warnings, - code, - message, - buffer.read_consistency, - buffer.read_int, - buffer.read_int, - buffer.read_int, - buffer.read_string) + cl = buffer.read_consistency + received = buffer.read_int + block_for = buffer.read_int + if protocol_version < 5 + WriteFailureErrorResponse.new(custom_payload, + warnings, + code, + message, + cl, + received, + block_for, + buffer.read_int, + buffer.read_string, + nil) + else + failures_by_node = buffer.read_reason_map + WriteFailureErrorResponse.new(custom_payload, + warnings, + code, + message, + cl, + received, + block_for, + nil, + buffer.read_string, + failures_by_node) + end when 0x2400 AlreadyExistsErrorResponse.new(custom_payload, warnings, diff --git a/spec/cassandra/cluster/options_spec.rb b/spec/cassandra/cluster/options_spec.rb index 6aff36295..4696031cd 100644 --- a/spec/cassandra/cluster/options_spec.rb +++ b/spec/cassandra/cluster/options_spec.rb @@ -22,46 +22,57 @@ def make_options(logger, protocol_version, connections_per_local_node, connections_per_remote_node, - requests_per_connection) + requests_per_connection, + allow_boolean_protocol) Cassandra::Cluster::Options.new( logger, protocol_version, nil, nil, nil, nil, nil, false, connections_per_local_node, connections_per_remote_node, 60, 30, true, 1, 10, - true, requests_per_connection, []) + true, requests_per_connection, [], allow_boolean_protocol) end module Cassandra class Cluster describe(Options) do let(:logger) { Cassandra::NullLogger.new } + it 'should set the protocol-version to max-supported if not in beta' do + expect(make_options(logger, nil, nil, nil, nil, false). + protocol_version).to eq(Cassandra::Protocol::Versions::MAX_SUPPORTED_VERSION) + end + + it 'should set the protocol-version to beta version if allow-beta is true' do + expect(make_options(logger, nil, nil, nil, nil, true). + protocol_version).to eq(Cassandra::Protocol::Versions::BETA_VERSION) + end + context :connections_per_local_node do it 'should return the default value for v2' do - expect(make_options(logger, 2, nil, nil, nil). + expect(make_options(logger, 2, nil, nil, nil, false). connections_per_local_node).to eq(2) end it 'should return the default value for v3' do - expect(make_options(logger, 3, nil, nil, nil). + expect(make_options(logger, 3, nil, nil, nil, false). connections_per_local_node).to eq(1) end it 'should return the user-specified value' do - expect(make_options(logger, 3, 12, nil, nil). + expect(make_options(logger, 3, 12, nil, nil, false). connections_per_local_node).to eq(12) - expect(make_options(logger, 2, 13, nil, nil). + expect(make_options(logger, 2, 13, nil, nil, false). connections_per_local_node).to eq(13) end end context :connections_per_remote_node do it 'should return the default value' do - expect(make_options(logger, 2, nil, nil, nil). + expect(make_options(logger, 2, nil, nil, nil, false). connections_per_remote_node).to eq(1) - expect(make_options(logger, 3, nil, nil, nil). + expect(make_options(logger, 3, nil, nil, nil, false). connections_per_remote_node).to eq(1) end it 'should return the user-specified value' do - expect(make_options(logger, 3, nil, 14, nil). + expect(make_options(logger, 3, nil, 14, nil, false). connections_per_remote_node).to eq(14) end end @@ -71,31 +82,31 @@ class Cluster it 'should return the default value for v2' do expect(logger).to_not receive(:warn) - expect(make_options(logger, 2, nil, nil, nil). + expect(make_options(logger, 2, nil, nil, nil, false). requests_per_connection).to eq(128) end it 'should return the default value for v3' do expect(logger).to_not receive(:warn) - expect(make_options(logger, 3, nil, nil, nil). + expect(make_options(logger, 3, nil, nil, nil, false). requests_per_connection).to eq(1024) end it 'should return the user-specified value' do expect(logger).to_not receive(:warn) - expect(make_options(logger, 2, nil, nil, 13). + expect(make_options(logger, 2, nil, nil, 13, false). requests_per_connection).to eq(13) - expect(make_options(logger, 3, nil, nil, 14). + expect(make_options(logger, 3, nil, nil, 14, false). requests_per_connection).to eq(14) end it 'should pull down the value to 128 if requested value is too high for v2' do expect(logger).to receive(:warn) - expect(make_options(logger, 2, nil, nil, 150). + expect(make_options(logger, 2, nil, nil, 150, false). requests_per_connection).to eq(128) end it 'should not adjust high value for v3' do expect(logger).to_not receive(:warn) - expect(make_options(logger, 3, nil, nil, 150). + expect(make_options(logger, 3, nil, nil, 150, false). requests_per_connection).to eq(150) end end diff --git a/spec/cassandra_spec.rb b/spec/cassandra_spec.rb index 87c7c566b..76bb138ca 100644 --- a/spec/cassandra_spec.rb +++ b/spec/cassandra_spec.rb @@ -399,12 +399,26 @@ def unavailable expect { C.validate(protocol_version: 'a') }.to raise_error(ArgumentError) expect { C.validate(protocol_version: 0) }.to raise_error(ArgumentError) expect { C.validate(protocol_version: 1.5) }.to raise_error(ArgumentError) - expect { C.validate(protocol_version: 5) }.to raise_error(ArgumentError) + expect { C.validate(protocol_version: Cassandra::Protocol::Versions::MAX_SUPPORTED_VERSION + 1) }. + to raise_error(ArgumentError) expect(C.validate(protocol_version: 1)).to eq({ protocol_version: 1 }) - expect(C.validate(protocol_version: 4)).to eq({ protocol_version: 4 }) + expect(C.validate(protocol_version: Cassandra::Protocol::Versions::MAX_SUPPORTED_VERSION)). + to eq({ protocol_version: Cassandra::Protocol::Versions::MAX_SUPPORTED_VERSION }) expect(C.validate(protocol_version: nil)).to eq({ protocol_version: nil }) end + it 'should validate that :protocol_version and :allow_beta_protocol are not both specified' do + expect(C.validate(allow_beta_protocol: true)).to eq({ allow_beta_protocol: true } ) + expect(C.validate(allow_beta_protocol: false)).to eq({ allow_beta_protocol: false } ) + expect { C.validate(allow_beta_protocol: true, protocol_version: 3) }.to raise_error(ArgumentError) + expect(C.validate(allow_beta_protocol: false, protocol_version: 3)). + to eq({ allow_beta_protocol: false, protocol_version: 3 }) + expect(C.validate(allow_beta_protocol: true, protocol_version: nil)). + to eq({ allow_beta_protocol: true, protocol_version: nil }) + expect(C.validate(allow_beta_protocol: false, protocol_version: nil)). + to eq({ allow_beta_protocol: false, protocol_version: nil }) + end + it 'should validate :futures_factory option' do class GoodFactory def error diff --git a/support/ccm.rb b/support/ccm.rb index a5ddf8d5f..54b6a67b8 100644 --- a/support/ccm.rb +++ b/support/ccm.rb @@ -721,7 +721,7 @@ def enable_triggers ccm_node_conf_dir = "~/.ccm/ruby-driver-cassandra-#{CCM.cassandra_version}-test-cluster" (1..@nodes.size).each do |n| - `mkdir #{ccm_node_conf_dir}/node#{n}/conf/triggers` + `mkdir -p #{ccm_node_conf_dir}/node#{n}/conf/triggers` `cp #{trigger_root}/AuditTrigger.properties #{ccm_node_conf_dir}/node#{n}/conf` `cp #{trigger_root}/trigger-example.jar #{ccm_node_conf_dir}/node#{n}/conf/triggers` end @@ -949,10 +949,13 @@ def switch_cluster(name) def create_cluster(name, version, datacenters, nodes_per_datacenter) nodes = Array.new(datacenters, nodes_per_datacenter).join(':') - create_args = ['-v', version, name] - create_args << '--dse' if @dse - - ccm.exec('create', *create_args) + if !@dse && ENV['CASSANDRA_DIR'] && !ENV['CASSANDRA_DIR'].empty? + ccm.exec('create', name, '--install-dir', ENV['CASSANDRA_DIR']) + else + create_args = ['-v', version, name] + create_args << '--dse' if @dse + ccm.exec('create', *create_args) + end config = [ '--rt', '1000', From a3d6309f84c20f7230104ef704d4ff156183627d Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 3 Oct 2016 13:47:01 -0700 Subject: [PATCH 142/196] RUBY-257 - prepared-statement cache should not be host-scoped. --- CHANGELOG.md | 4 ++ lib/cassandra/cluster/client.rb | 65 +++++++-------------------- spec/cassandra/cluster/client_spec.rb | 26 ++++++++--- spec/regressions/RUBY-189_spec.rb | 29 +++++------- 4 files changed, 52 insertions(+), 72 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07a7afa97..9c5a51205 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ Features: * Update Table metadata to include trigger-collection and view-collection metadata. * Added execution profiles to encapsulate a group of request execution options. * Added support for v5 beta protocol. +* Make prepared statement cache not be scoped by host and optimistically execute prepared statements on hosts where + we are not sure the statement is already prepared. The motivation is that in the steady state, all nodes have + prepared statements already, so there is no need to prepare statements before executing them. If the guess is wrong, + the client will prepare and execute at that point. Bug Fixes: * [RUBY-255](https://datastax-oss.atlassian.net/browse/RUBY-255) ControlConnection.peer_ip ignores peers that are missing critical information in system.peers. diff --git a/lib/cassandra/cluster/client.rb b/lib/cassandra/cluster/client.rb index 77902e271..42a6886c7 100644 --- a/lib/cassandra/cluster/client.rb +++ b/lib/cassandra/cluster/client.rb @@ -47,7 +47,7 @@ def initialize(logger, @futures = futures_factory @connections = ::Hash.new @prepared_statements = ::Hash.new - @preparing_statements = ::Hash.new + @preparing_statements = ::Hash.new {|hash, host| hash[host] = {}} @pending_connections = ::Hash.new @keyspace = nil @state = :idle @@ -83,7 +83,6 @@ def connect connecting_hosts[host] = pool_size @pending_connections[host] = 0 - @prepared_statements[host] = {} @preparing_statements[host] = {} @connections[host] = ConnectionPool.new end @@ -182,7 +181,6 @@ def host_up(host) end @pending_connections[host] ||= 0 - @prepared_statements[host] = {} @preparing_statements[host] = {} @connections[host] = ConnectionPool.new end @@ -197,7 +195,6 @@ def host_down(host) return Ione::Future.resolved unless @connections.key?(host) @pending_connections.delete(host) unless @pending_connections[host] > 0 - @prepared_statements.delete(host) @preparing_statements.delete(host) pool = @connections.delete(host) end @@ -632,15 +629,13 @@ def prepare_and_send_request_by_plan(host, errors, hosts) cql = statement.cql - id = nil - host_is_up = true - synchronize do - if @prepared_statements[host].nil? - host_is_up = false - else - id = @prepared_statements[host][cql] - end - end + + # Get the prepared statement id for this statement from our cache if possible. We are optimistic + # that the statement has previously been prepared on all hosts, so the id will be valid. However, if + # we're in the midst of preparing the statement on the given host, we know that executing with the id + # will fail. So, act like we don't have the prepared-statement id in that case. + + id = synchronize { @preparing_statements[host][cql] ? nil : @prepared_statements[cql] } if id request.id = id @@ -655,19 +650,6 @@ def prepare_and_send_request_by_plan(host, timeout, errors, hosts) - elsif !host_is_up - # We've hit a race condition where the plan says we can query this host, but the host has gone - # down in the mean time. Just execute the plan again on the next host. - @logger.debug("#{host} is down; executing plan on next host") - execute_by_plan(promise, - keyspace, - statement, - options, - request, - plan, - timeout, - errors, - hosts) else prepare = prepare_statement(host, connection, cql, timeout) prepare.on_complete do |_| @@ -825,29 +807,15 @@ def batch_and_send_request_by_plan(host, cql = statement.cql if statement.is_a?(Statements::Bound) - host_is_up = true - id = nil - synchronize do - if @prepared_statements[host].nil? - host_is_up = false - else - id = @prepared_statements[host][cql] - end - end + # Get the prepared statement id for this statement from our cache if possible. We are optimistic + # that the statement has previously been prepared on all hosts, so the id will be valid. However, if + # we're in the midst of preparing the statement on the given host, we know that executing with the id + # will fail. So, act like we don't have the prepared-statement id in that case. + + id = synchronize { @preparing_statements[host][cql] ? nil : @prepared_statements[cql] } if id request.add_prepared(id, statement.params, statement.params_types) - elsif !host_is_up - @logger.debug("#{host} is down; executing on next host in plan") - return batch_by_plan(promise, - keyspace, - batch_statement, - options, - request, - plan, - timeout, - errors, - hosts) else unprepared[cql] << statement end @@ -1097,7 +1065,6 @@ def handle_response(response_future, synchronize do @preparing_statements[host].delete(cql) - @prepared_statements[host].delete(cql) end prepare = prepare_statement(host, connection, cql, timeout) @@ -1198,7 +1165,7 @@ def handle_response(response_future, when Protocol::PreparedResultResponse cql = request.cql synchronize do - @prepared_statements[host][cql] = r.id + @prepared_statements[cql] = r.id @preparing_statements[host].delete(cql) end @@ -1531,7 +1498,7 @@ def prepare_statement(host, connection, cql, timeout) when Protocol::PreparedResultResponse id = r.id synchronize do - @prepared_statements[host][cql] = id + @prepared_statements[cql] = id @preparing_statements[host].delete(cql) end id diff --git a/spec/cassandra/cluster/client_spec.rb b/spec/cassandra/cluster/client_spec.rb index c8700af7d..63ca6bcff 100644 --- a/spec/cassandra/cluster/client_spec.rb +++ b/spec/cassandra/cluster/client_spec.rb @@ -695,7 +695,7 @@ class Cluster expect(sent).to be_truthy end - it 're-prepares a statement on new connection' do + it 'does not re-prepare a statement on new connection if the new host already has the statement prepared' do count = 0 io_reactor.on_connection do |connection| connection.handle_request do |request| @@ -719,7 +719,12 @@ class Cluster cluster_registry.hosts.delete(cluster_registry.hosts.first) client.execute(statement.bind, execution_options).get - expect(count).to eq(2) + + # Expected sequence of events: + # 1. prepare on host1 + # 2. execute on host2. Since execute succeeds, the implication is that host2 already had the statement + # prepared. + expect(count).to eq(1) end it 're-prepares a statement on unprepared error' do @@ -752,7 +757,14 @@ class Cluster cluster_registry.hosts.delete(statement.execution_info.hosts.first) client.execute(statement.bind, execution_options).get - expect(count).to eq(3) + + + # Expected sequence of events: + # 1. prepare on host1 + # 2. execute on host2, yielding unprepared error. + # 3. prepare on host2 + # 4. execute on host2. + expect(count).to eq(2) expect(error).to be(false) end @@ -937,7 +949,7 @@ class Cluster expect(sent).to be_truthy end - it 'automatically re-prepares statements' do + it 'does not automatically re-prepare statements' do sent = false count = 0 batch = Statements::Batch::Logged.new(driver.execution_options) @@ -983,7 +995,11 @@ class Cluster client.batch(batch, execution_options.override(consistency: :one)).get expect(sent).to be_truthy - expect(count).to eq(2) + + # Expected sequence of events: + # 1. prepare on host1 + # 2. run batch on host2 with prepared-statement id's. This succeeds, so no more work to do. + expect(count).to eq(1) end it 'follows the plan on failure' do diff --git a/spec/regressions/RUBY-189_spec.rb b/spec/regressions/RUBY-189_spec.rb index decf98d92..0837ba806 100644 --- a/spec/regressions/RUBY-189_spec.rb +++ b/spec/regressions/RUBY-189_spec.rb @@ -56,19 +56,17 @@ class Cluster let(:request) { double('request') } let(:batch_statement) { double('batch_statement') } let(:bound_statement) { double('bound_statement') } + let(:response) { double('response future')} + let(:prepared_id) { 1234 } it 'RUBY-189 - handles node down after prepare' do expect(promise).to_not receive(:break) expect(statement).to receive(:cql).and_return('select * from foo') - expect(client).to receive(:execute_by_plan).with(promise, - 'keyspace', - statement, - options, - 'request', - plan, - 12, - errors, - hosts) + + # We expect the down node to receive a request attempt. + expect(connection).to receive(:send_request).and_return(response) + expect(response).to receive(:map).and_return(response) + expect(response).to receive(:on_complete) client.send(:prepare_and_send_request_by_plan, 'down_host', connection, @@ -85,19 +83,14 @@ class Cluster it 'RUBY-189 - handles node down after prepare in batch' do expect(request).to receive(:clear) + expect(connection).to receive(:send_request).and_return(response) + expect(response).to receive(:map).and_return(response) + expect(response).to receive(:on_complete) + expect(bound_statement).to receive(:is_a?).and_return(true) expect(bound_statement).to receive(:cql).and_return('select * from foo') expect(batch_statement).to receive(:statements).and_return([bound_statement]) expect(promise).to_not receive(:break) - expect(client).to receive(:batch_by_plan).with(promise, - 'keyspace', - batch_statement, - options, - request, - plan, - 12, - errors, - hosts) client.send(:batch_and_send_request_by_plan, 'down_host', connection, From 76d3b7f55636b87860eb7cb6a305f31d676d166b Mon Sep 17 00:00:00 2001 From: Kishan Karunaratne Date: Mon, 3 Oct 2016 14:59:15 -0700 Subject: [PATCH 143/196] Upgrade CI to test against C* 3.9 --- build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.yaml b/build.yaml index 5afc28062..f81286ba1 100644 --- a/build.yaml +++ b/build.yaml @@ -23,7 +23,7 @@ cassandra: - 2.1 - 2.2 - 3.0 - - 3.7 + - 3.9 os: - ubuntu/trusty64 build: From 2daf0c67b5468816b7e62eb8c9f49c6a8f7159c8 Mon Sep 17 00:00:00 2001 From: Kishan Karunaratne Date: Mon, 3 Oct 2016 16:07:31 -0700 Subject: [PATCH 144/196] Change to ProtocolError in session test --- integration/session_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration/session_test.rb b/integration/session_test.rb index fa79d9193..3cdb52534 100644 --- a/integration/session_test.rb +++ b/integration/session_test.rb @@ -809,7 +809,7 @@ def test_can_set_protocol_version_explicitly # C* 3.0+ does not support protocol version < 3 if CCM.cassandra_version >= '3.0.0' - assert_raises(Cassandra::Errors::NoHostsAvailable) do + assert_raises(Cassandra::Errors::ProtocolError) do Cassandra.cluster(protocol_version: 2) end end From 5a6fcb5544f44d90a2a74f1db60ce984cde7f1d4 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 3 Oct 2016 17:40:40 -0700 Subject: [PATCH 145/196] RUBY-257 - prepared-statement cache should not be host-scoped. * Fixed some issues in batch request handling. --- lib/cassandra/cluster/client.rb | 20 ++++++++++++++------ lib/cassandra/statements/bound.rb | 6 +++++- lib/cassandra/statements/prepared.rb | 9 +++++++-- spec/cassandra/session_spec.rb | 4 ++-- 4 files changed, 28 insertions(+), 11 deletions(-) diff --git a/lib/cassandra/cluster/client.rb b/lib/cassandra/cluster/client.rb index 42a6886c7..a52d4bff1 100644 --- a/lib/cassandra/cluster/client.rb +++ b/lib/cassandra/cluster/client.rb @@ -1061,16 +1061,23 @@ def handle_response(response_future, r.data_present, retries) when Protocol::UnpreparedErrorResponse - cql = statement.cql - - synchronize do - @preparing_statements[host].delete(cql) + cql = nil + if statement.is_a?(Cassandra::Statements::Batch) + # Find the prepared statement with the prepared-statement-id reported by the node. + unprepared_child = statement.statements.select do |s| + (s.is_a?(Cassandra::Statements::Prepared) || s.is_a?(Cassandra::Statements::Bound)) && s.id == r.id + end.first + cql = unprepared_child ? unprepared_child.cql : nil + else + # This is a normal statement, so we have everything we need. + cql = statement.cql + synchronize { @preparing_statements[host].delete(cql) } end prepare = prepare_statement(host, connection, cql, timeout) prepare.on_complete do |_| if prepare.resolved? - request.id = prepare.value + request.id = prepare.value unless request.is_a?(Cassandra::Protocol::BatchRequest) do_send_request_by_plan(host, connection, promise, @@ -1174,7 +1181,8 @@ def handle_response(response_future, pk_idx ||= @schema.get_pk_idx(metadata) promise.fulfill( - Statements::Prepared.new(r.custom_payload, + Statements::Prepared.new(r.id, + r.custom_payload, r.warnings, cql, metadata, diff --git a/lib/cassandra/statements/bound.rb b/lib/cassandra/statements/bound.rb index afd70cc8d..2ab7e9859 100644 --- a/lib/cassandra/statements/bound.rb +++ b/lib/cassandra/statements/bound.rb @@ -28,15 +28,19 @@ class Bound attr_reader :params # @private attr_reader :params_types, :result_metadata, :keyspace, :partition_key + # @private prepared-statement id + attr_reader :id # @private - def initialize(cql, + def initialize(id, + cql, params_types, result_metadata, params, keyspace = nil, partition_key = nil, idempotent = false) + @id = id @cql = cql @params_types = params_types @result_metadata = result_metadata diff --git a/lib/cassandra/statements/prepared.rb b/lib/cassandra/statements/prepared.rb index b881d0bed..a9d89eb0e 100644 --- a/lib/cassandra/statements/prepared.rb +++ b/lib/cassandra/statements/prepared.rb @@ -27,9 +27,12 @@ class Prepared attr_reader :cql # @private attr_reader :result_metadata + # @private prepared-statement id + attr_reader :id # @private - def initialize(payload, + def initialize(id, + payload, warnings, cql, params_metadata, @@ -44,6 +47,7 @@ def initialize(payload, retries, client, connection_options) + @id = id @payload = payload @warnings = warnings @cql = cql @@ -131,7 +135,8 @@ def bind(args = nil) partition_key = create_partition_key(params) - Bound.new(@cql, + Bound.new(@id, + @cql, param_types, @result_metadata, params, diff --git a/spec/cassandra/session_spec.rb b/spec/cassandra/session_spec.rb index a8ab252b7..cfa70fb72 100644 --- a/spec/cassandra/session_spec.rb +++ b/spec/cassandra/session_spec.rb @@ -131,7 +131,7 @@ module Cassandra let(:cql) { "INSERT INTO songs (id, title, album, artist, tags) VALUES (?, ?, ?, ?, ?)" } let(:result_metadata) { nil } let(:params_metadata) { Array.new(5) } - let(:statement) { Statements::Prepared.new(nil, nil, cql, params_metadata, result_metadata, nil, nil, nil, nil, VOID_OPTIONS, nil, nil, nil, nil, nil) } + let(:statement) { Statements::Prepared.new(nil, nil, nil, cql, params_metadata, result_metadata, nil, nil, nil, nil, VOID_OPTIONS, nil, nil, nil, nil, nil) } it 'binds and executes result' do promise = double('promise') @@ -151,7 +151,7 @@ module Cassandra let(:result_metadata) { nil } let(:params_metadata) { Array.new(5) } let(:params) { [1,2,3,4,5] } - let(:statement) { Statements::Bound.new(cql, params_metadata, result_metadata, params) } + let(:statement) { Statements::Bound.new(nil, cql, params_metadata, result_metadata, params) } it 'executes statement' do promise = double('promise') From f7ebc74de97f554042e8e2313c8b0c5140dfbb7e Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Wed, 5 Oct 2016 13:13:04 -0700 Subject: [PATCH 146/196] RUBY-163 - Cucumber tests fail in JRuby9k after encountering non-English characters. --- build.yaml | 5 +- features/support/env.rb | 104 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 104 insertions(+), 5 deletions(-) diff --git a/build.yaml b/build.yaml index f81286ba1..a695a711f 100644 --- a/build.yaml +++ b/build.yaml @@ -3,15 +3,12 @@ schedules: schedule: per_commit matrix: exclude: - - ruby: ['2.2', 'jruby9k'] + - ruby: ['2.2'] - cassandra: ['2.0', '2.2', '3.0'] nightly: schedule: nightly branches: include: [master] - matrix: - exclude: - - ruby: jruby9k ruby: - 2.2 diff --git a/features/support/env.rb b/features/support/env.rb index c3194bb2c..b2421971b 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -56,6 +56,7 @@ def to_str Aruba.configure do |config| config.exit_timeout = 60 + config.remove_ansi_escape_sequences = false end Before do @@ -82,4 +83,105 @@ def to_str After('@client_failures') do @cluster.restart -end \ No newline at end of file +end + +############################################################################################################## +# The following matchers and aruba SpawnProcess monkey-patch cause aruba to write command output to file +# with binmode to avoid encoding / translation issues. The matchers take the "binary" output and re-interpret +# as utf8. This works around (resolves?) a behavior difference in jruby9k where cukes were failing when output +# included non-ascii characters. RUBY-167 +############################################################################################################## + +RSpec::Matchers.define :include_output_string do |expected| + match do |actual| + actual.force_encoding("UTF-8") + @expected = Regexp.new(Regexp.escape(sanitize_text(expected.to_s)), Regexp::MULTILINE) + @actual = sanitize_text(actual) + + values_match? @expected, @actual + end + + diffable + + description { "string includes: #{description_of expected}" } +end + +RSpec::Matchers.define :match_output_string do |expected| + match do |actual| + actual.force_encoding("UTF-8") + @expected = Regexp.new(unescape_text(expected), Regexp::MULTILINE) + @actual = sanitize_text(actual) + + values_match? @expected, @actual + end + + diffable + + description { "output string matches: #{description_of expected}" } +end + +RSpec::Matchers.define :output_string_eq do |expected| + match do |actual| + actual.force_encoding("UTF-8") + @expected = sanitize_text(expected.to_s) + @actual = sanitize_text(actual.to_s) + + values_match? @expected, @actual + end + + diffable + + description { "output string is eq: #{description_of expected}" } +end + +module Aruba + module Processes + class SpawnProcess + def start + # rubocop:disable Metrics/LineLength + fail CommandAlreadyStartedError, %(Command "#{commandline}" has already been started. Please `#stop` the command first and `#start` it again. Alternatively use `#restart`.\n#{caller.join("\n")}) if started? + # rubocop:enable Metrics/LineLength + + @started = true + + @process = ChildProcess.build(*[command_string.to_a, arguments].flatten) + @stdout_file = Tempfile.new('aruba-stdout-') + @stderr_file = Tempfile.new('aruba-stderr-') + + + @stdout_file.sync = true + @stderr_file.sync = true + + @stdout_file.binmode + @stderr_file.binmode + + @exit_status = nil + @duplex = true + + before_run + + @process.leader = true + @process.io.stdout = @stdout_file + @process.io.stderr = @stderr_file + @process.duplex = @duplex + @process.cwd = @working_directory + + @process.environment.update(environment) + + begin + Aruba.platform.with_environment(environment) do + @process.start + sleep startup_wait_time + end + rescue ChildProcess::LaunchError => e + raise LaunchError, "It tried to start #{cmd}. " + e.message + end + + after_run + + yield self if block_given? + end + end + end +end + From c3d9542176d01c64b8fb54416f508055e6b202b2 Mon Sep 17 00:00:00 2001 From: Kishan Karunaratne Date: Thu, 6 Oct 2016 17:16:52 -0700 Subject: [PATCH 147/196] Swap JRuby 9k for JRuby 1.7 in per_commit CI build --- build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.yaml b/build.yaml index a695a711f..1422de55d 100644 --- a/build.yaml +++ b/build.yaml @@ -3,7 +3,7 @@ schedules: schedule: per_commit matrix: exclude: - - ruby: ['2.2'] + - ruby: ['2.2', 'jruby1.7'] - cassandra: ['2.0', '2.2', '3.0'] nightly: schedule: nightly From 45503921db598f3a05da6628e862f5f0c8e725f3 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 10 Oct 2016 17:54:26 -0700 Subject: [PATCH 148/196] RUBY-250 - Expose various cluster attributes via getters in the api. --- CHANGELOG.md | 1 + lib/cassandra/cluster.rb | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c5a51205..bbbcbefa3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Features: we are not sure the statement is already prepared. The motivation is that in the steady state, all nodes have prepared statements already, so there is no need to prepare statements before executing them. If the guess is wrong, the client will prepare and execute at that point. +* Expose various cluster attributes with getters. Bug Fixes: * [RUBY-255](https://datastax-oss.atlassian.net/browse/RUBY-255) ControlConnection.peer_ip ignores peers that are missing critical information in system.peers. diff --git a/lib/cassandra/cluster.rb b/lib/cassandra/cluster.rb index 78f4755d1..fcf7b5fcd 100644 --- a/lib/cassandra/cluster.rb +++ b/lib/cassandra/cluster.rb @@ -151,6 +151,21 @@ def each_keyspace(&block) # @return [Boolean] true or false def_delegators :@schema, :keyspace, :has_keyspace? + # @return [Integer] Cassandra native protocol port + def port + @connection_options.port + end + + # @return [Integer] the version of the native protocol used in communication with nodes + def protocol_version + @connection_options.protocol_version + end + + # @return [Hash] the collection of execution profiles + def execution_profiles + @profile_manager.profiles + end + # @!method refresh_schema_async # Trigger an asynchronous schema metadata refresh # @return [Cassandra::Future] a future that will be fulfilled when @@ -279,7 +294,7 @@ def inspect "name=#{name.inspect}, " \ "port=#{@connection_options.port}, " \ "protocol_version=#{@connection_options.protocol_version}, " \ - "profile_manager=#{@profile_manager.inspect}, " \ + "execution_profiles=#{@profile_manager.profiles.inspect}, " \ "hosts=#{hosts.inspect}, " \ "keyspaces=#{keyspaces.inspect}>" end From 89e2e6f1d1bb3af48d2ae503642cfb12da04f7a9 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Tue, 11 Oct 2016 17:40:49 -0700 Subject: [PATCH 149/196] RUBY-264 - Table erroneously reported as using compact storage --- CHANGELOG.md | 1 + integration/metadata_test.rb | 25 +++++++++++++++++++++++- lib/cassandra/cluster/schema/fetchers.rb | 2 +- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bbbcbefa3..3d187e90a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Features: Bug Fixes: * [RUBY-255](https://datastax-oss.atlassian.net/browse/RUBY-255) ControlConnection.peer_ip ignores peers that are missing critical information in system.peers. +* [RUBY-264](https://datastax-oss.atlassian.net/browse/RUBY-255) Table erroneously reported as using compact storage. # 3.0.3 diff --git a/integration/metadata_test.rb b/integration/metadata_test.rb index 747ccaba1..b0235d12c 100644 --- a/integration/metadata_test.rb +++ b/integration/metadata_test.rb @@ -45,6 +45,12 @@ def setup @listener.wait_for_table('simplex', 'test2') @session.execute("CREATE TABLE simplex.audit (key timeuuid, keyspace_name text, table_name text, primary_key text, PRIMARY KEY(key))") @listener.wait_for_table('simplex', 'audit') + @session.execute(< Date: Wed, 12 Oct 2016 13:34:37 -0700 Subject: [PATCH 150/196] RUBY-235 - execution_info.retries should be the total number of retries of this request rather than the number of retries on the last node. --- CHANGELOG.md | 3 +- lib/cassandra/cluster/client.rb | 117 ++++++++++++++++++++---------- spec/regressions/RUBY-189_spec.rb | 6 +- 3 files changed, 85 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d187e90a..565201aeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,8 +12,9 @@ Features: * Expose various cluster attributes with getters. Bug Fixes: +* [RUBY-235](https://datastax-oss.atlassian.net/browse/RUBY-235) execution_info.retries resets retry count when switching hosts. * [RUBY-255](https://datastax-oss.atlassian.net/browse/RUBY-255) ControlConnection.peer_ip ignores peers that are missing critical information in system.peers. -* [RUBY-264](https://datastax-oss.atlassian.net/browse/RUBY-255) Table erroneously reported as using compact storage. +* [RUBY-264](https://datastax-oss.atlassian.net/browse/RUBY-264) Table erroneously reported as using compact storage. # 3.0.3 diff --git a/lib/cassandra/cluster/client.rb b/lib/cassandra/cluster/client.rb index a52d4bff1..a5b421022 100644 --- a/lib/cassandra/cluster/client.rb +++ b/lib/cassandra/cluster/client.rb @@ -528,13 +528,16 @@ def execute_by_plan(promise, plan, timeout, errors = nil, - hosts = []) + hosts = [], + retries = -1) unless plan.has_next? promise.break(Errors::NoHostsAvailable.new(errors)) return end hosts << host = plan.next + retries += 1 + pool = nil synchronize { pool = @connections[host] } @@ -549,7 +552,8 @@ def execute_by_plan(promise, plan, timeout, errors, - hosts) + hosts, + retries) end connection = pool.random_connection @@ -568,7 +572,8 @@ def execute_by_plan(promise, plan, timeout, errors, - hosts) + hosts, + retries) else s.on_failure do |e| if e.is_a?(Errors::HostError) || @@ -583,7 +588,8 @@ def execute_by_plan(promise, plan, timeout, errors, - hosts) + hosts, + retries) else promise.break(e) end @@ -601,7 +607,8 @@ def execute_by_plan(promise, plan, timeout, errors, - hosts) + hosts, + retries) end rescue => e errors ||= {} @@ -614,7 +621,8 @@ def execute_by_plan(promise, plan, timeout, errors, - hosts) + hosts, + retries) end def prepare_and_send_request_by_plan(host, @@ -627,7 +635,8 @@ def prepare_and_send_request_by_plan(host, plan, timeout, errors, - hosts) + hosts, + retries) cql = statement.cql # Get the prepared statement id for this statement from our cache if possible. We are optimistic @@ -649,7 +658,8 @@ def prepare_and_send_request_by_plan(host, plan, timeout, errors, - hosts) + hosts, + retries) else prepare = prepare_statement(host, connection, cql, timeout) prepare.on_complete do |_| @@ -665,7 +675,8 @@ def prepare_and_send_request_by_plan(host, plan, timeout, errors, - hosts) + hosts, + retries) else prepare.on_failure do |e| if e.is_a?(Errors::HostError) || @@ -680,7 +691,8 @@ def prepare_and_send_request_by_plan(host, plan, timeout, errors, - hosts) + hosts, + retries) else promise.break(e) end @@ -700,13 +712,15 @@ def batch_by_plan(promise, plan, timeout, errors = nil, - hosts = []) + hosts = [], + retries = -1) unless plan.has_next? promise.break(Errors::NoHostsAvailable.new(errors)) return end hosts << host = plan.next + retries += 1 pool = nil synchronize { pool = @connections[host] } @@ -721,7 +735,8 @@ def batch_by_plan(promise, plan, timeout, errors, - hosts) + hosts, + retries) end connection = pool.random_connection @@ -740,7 +755,8 @@ def batch_by_plan(promise, plan, timeout, errors, - hosts) + hosts, + retries) else s.on_failure do |e| if e.is_a?(Errors::HostError) || @@ -755,7 +771,8 @@ def batch_by_plan(promise, plan, timeout, errors, - hosts) + hosts, + retries) else promise.break(e) end @@ -773,7 +790,8 @@ def batch_by_plan(promise, plan, timeout, errors, - hosts) + hosts, + retries) end rescue => e errors ||= {} @@ -786,7 +804,8 @@ def batch_by_plan(promise, plan, timeout, errors, - hosts) + hosts, + retries) end def batch_and_send_request_by_plan(host, @@ -799,7 +818,8 @@ def batch_and_send_request_by_plan(host, plan, timeout, errors, - hosts) + hosts, + retries) request.clear unprepared = Hash.new {|hash, cql| hash[cql] = []} @@ -835,7 +855,8 @@ def batch_and_send_request_by_plan(host, plan, timeout, errors, - hosts) + hosts, + retries) else to_prepare = unprepared.to_a futures = to_prepare.map do |cql, _| @@ -863,7 +884,8 @@ def batch_and_send_request_by_plan(host, plan, timeout, errors, - hosts) + hosts, + retries) else f.on_failure do |e| if e.is_a?(Errors::HostError) || @@ -878,7 +900,8 @@ def batch_and_send_request_by_plan(host, plan, timeout, errors, - hosts) + hosts, + retries) else promise.break(e) end @@ -896,13 +919,15 @@ def send_request_by_plan(promise, plan, timeout, errors = nil, - hosts = []) + hosts = [], + retries = -1) unless plan.has_next? promise.break(Errors::NoHostsAvailable.new(errors)) return end hosts << host = plan.next + retries += 1 pool = nil synchronize { pool = @connections[host] } @@ -917,7 +942,8 @@ def send_request_by_plan(promise, plan, timeout, errors, - hosts) + hosts, + retries) end connection = pool.random_connection @@ -936,7 +962,8 @@ def send_request_by_plan(promise, plan, timeout, errors, - hosts) + hosts, + retries) else s.on_failure do |e| if e.is_a?(Errors::HostError) || @@ -951,7 +978,8 @@ def send_request_by_plan(promise, plan, timeout, errors, - hosts) + hosts, + retries) else promise.break(e) end @@ -969,7 +997,8 @@ def send_request_by_plan(promise, plan, timeout, errors, - hosts) + hosts, + retries) end rescue => e errors ||= {} @@ -982,7 +1011,8 @@ def send_request_by_plan(promise, plan, timeout, errors, - hosts) + hosts, + retries) end def do_send_request_by_plan(host, @@ -996,7 +1026,7 @@ def do_send_request_by_plan(host, timeout, errors, hosts, - retries = 0) + retries) request.retries = retries f = connection.send_request(request, timeout) @@ -1088,7 +1118,8 @@ def handle_response(response_future, plan, timeout, errors, - hosts) + hosts, + retries) else prepare.on_failure do |e| if e.is_a?(Errors::HostError) || @@ -1102,7 +1133,8 @@ def handle_response(response_future, plan, timeout, errors, - hosts) + hosts, + retries) else promise.break(e) end @@ -1131,7 +1163,8 @@ def handle_response(response_future, plan, timeout, errors, - hosts) + hosts, + retries) when Protocol::ExecuteRequest execute_by_plan(promise, keyspace, @@ -1141,7 +1174,8 @@ def handle_response(response_future, plan, timeout, errors, - hosts) + hosts, + retries) when Protocol::BatchRequest batch_by_plan(promise, keyspace, @@ -1151,7 +1185,8 @@ def handle_response(response_future, plan, timeout, errors, - hosts) + hosts, + retries) end else promise.break(error) @@ -1303,7 +1338,8 @@ def handle_response(response_future, plan, timeout, errors, - hosts) + hosts, + retries) when Protocol::ExecuteRequest execute_by_plan(promise, keyspace, @@ -1313,7 +1349,8 @@ def handle_response(response_future, plan, timeout, errors, - hosts) + hosts, + retries) when Protocol::BatchRequest batch_by_plan(promise, keyspace, @@ -1323,7 +1360,8 @@ def handle_response(response_future, plan, timeout, errors, - hosts) + hosts, + retries) else promise.break(e) end @@ -1377,7 +1415,8 @@ def handle_response(response_future, plan, timeout, errors, - hosts) + hosts, + retries) when Protocol::ExecuteRequest execute_by_plan(promise, keyspace, @@ -1387,7 +1426,8 @@ def handle_response(response_future, plan, timeout, errors, - hosts) + hosts, + retries) when Protocol::BatchRequest batch_by_plan(promise, keyspace, @@ -1397,7 +1437,8 @@ def handle_response(response_future, plan, timeout, errors, - hosts) + hosts, + retries) else promise.break(ex) end diff --git a/spec/regressions/RUBY-189_spec.rb b/spec/regressions/RUBY-189_spec.rb index 0837ba806..c6ac9ae79 100644 --- a/spec/regressions/RUBY-189_spec.rb +++ b/spec/regressions/RUBY-189_spec.rb @@ -78,7 +78,8 @@ class Cluster plan, 12, errors, - hosts) + hosts, + 0) end it 'RUBY-189 - handles node down after prepare in batch' do @@ -102,7 +103,8 @@ class Cluster plan, 12, errors, - hosts) + hosts, + 0) end end end From 78a73cd2ec246f3ad946dc1ca933b7f1ee421a37 Mon Sep 17 00:00:00 2001 From: Evan Prothro Date: Mon, 17 Oct 2016 16:37:22 -0500 Subject: [PATCH 151/196] fixup a few ruby warnings --- .gitignore | 2 ++ CONTRIBUTING.md | 28 +++++++++++++++------ lib/cassandra/cluster/control_connection.rb | 2 ++ lib/cassandra/executors.rb | 2 +- lib/cassandra/uuid/generator.rb | 9 ++++--- spec/spec_helper.rb | 3 +++ 6 files changed, 34 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index d30f3fc79..98ec99e8d 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ /venv /lib/cassandra_murmur3.* /node_modules +/*.gem +/.bundle/* \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7b4cdcb24..f01fca5b8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,18 +5,30 @@ All code has bugs, but if you report them they can be squashed. The best bug reports include everything that is needed to reliably reproduce the bug. + +### Running the Test Suite + Try to write a test case and include it in your report (have a look at the -[regression test suite](spec/integration/regression_spec.rb) if you need inspiration). -Submit defect reports to our [Jira](https://datastax-oss.atlassian.net/projects/RUBY/issues). +[regression test suite](spec/regressions) if you need inspiration). + +1. Bundle with `bundle install` +1. Run the unit test suite with `rake rspec` + * Using this rake task will install necessary ruby extensions as a prerequisite + * For `bundle exec rspec` to be successful, run `bundle exec rake compile` once, beforehand If it's not possible to write a test case, for example because the bug only happens in -very particular circumstances, or is not deterministic, make sure you include as much -information as you can about the situation. The version of the ruby driver is an absolute -must, the version of Ruby and Cassandra are also very important. If there is a stack trace -from the error make sure to include that (unfortunately the asynchronous nature of the -ruby driver means that the stack traces are not always as revealing as they could be). +very particular circumstances, or is not deterministic, please still report the bug! + +### Opening a ticket + +Submit defect reports to our [Jira](https://datastax-oss.atlassian.net/projects/RUBY/issues). Include: + +* The `cassandra-driver` version (`bundle exec gem cassandra-driver -v`) +* The Ruby version (`ruby -v`) +* The Cassandra version (2nd line printed when running `cqlsh`) +* A stack trace from the error, if there is one -##Pull Requests +## Pull Requests If you're able to fix a bug yourself, you can [fork the repository](https://help.github.com/articles/fork-a-repo/) and submit a diff --git a/lib/cassandra/cluster/control_connection.rb b/lib/cassandra/cluster/control_connection.rb index 39b5fefa3..6179a8da4 100644 --- a/lib/cassandra/cluster/control_connection.rb +++ b/lib/cassandra/cluster/control_connection.rb @@ -36,8 +36,10 @@ def initialize(logger, io_reactor, cluster_registry, cluster_schema, @address_resolver = address_resolution_policy @connector = connector @connection_options = connection_options + @connection = nil @schema_fetcher = schema_fetcher @refreshing_statuses = ::Hash.new(false) + @refresh_schema_future = nil @status = :closed @refreshing_hosts = false @refreshing_host = ::Hash.new(false) diff --git a/lib/cassandra/executors.rb b/lib/cassandra/executors.rb index 66d2f3764..e07282a29 100644 --- a/lib/cassandra/executors.rb +++ b/lib/cassandra/executors.rb @@ -43,8 +43,8 @@ def initialize(size) @cond = new_cond @tasks = ::Array.new @waiting = 0 - @pool = ::Array.new(size, &method(:spawn_thread)) @term = false + @pool = ::Array.new(size, &method(:spawn_thread)) end def execute(*args, &block) diff --git a/lib/cassandra/uuid/generator.rb b/lib/cassandra/uuid/generator.rb index 605b8af2c..3e53c1dcf 100644 --- a/lib/cassandra/uuid/generator.rb +++ b/lib/cassandra/uuid/generator.rb @@ -49,9 +49,10 @@ def initialize(node_id = (::SecureRandom.random_number(2**47) | 0x010000000000), clock = ::Time) raise ::ArgumentError, 'invalid clock' unless clock.respond_to?(:now) - @node_id = Integer(node_id) - @clock_id = Integer(clock_id) - @clock = clock + @node_id = Integer(node_id) + @clock_id = Integer(clock_id) + @clock = clock + @last_usecs = nil end # Returns a new UUID with a time component that is the current time. @@ -82,6 +83,7 @@ def initialize(node_id = (::SecureRandom.random_number(2**47) | 0x010000000000), def now now = @clock.now usecs = now.to_i * 1_000_000 + now.usec + if @last_usecs && @last_usecs - @sequence <= usecs && usecs <= @last_usecs @sequence += 1 elsif @last_usecs && @last_usecs > usecs @@ -90,6 +92,7 @@ def now else @sequence = 0 end + @last_usecs = usecs + @sequence from_usecs(@last_usecs) end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 641cbde3a..1e44c6f7b 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -33,6 +33,9 @@ require 'support/stub_io_reactor' RSpec.configure do |config| + # suppress ruby warnings + config.warnings = false + config.expect_with(:rspec) do |c| c.syntax = [:should, :expect] end From 0964511298f0a9db0f011a18928f4462a47dc737 Mon Sep 17 00:00:00 2001 From: Evan Prothro Date: Mon, 17 Oct 2016 20:19:27 -0500 Subject: [PATCH 152/196] fixup --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f01fca5b8..ac9ba1b26 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,7 +23,7 @@ very particular circumstances, or is not deterministic, please still report the Submit defect reports to our [Jira](https://datastax-oss.atlassian.net/projects/RUBY/issues). Include: -* The `cassandra-driver` version (`bundle exec gem cassandra-driver -v`) +* The `cassandra-driver` version (`bundle show cassandra-driver | sed 's/.*\///'`) * The Ruby version (`ruby -v`) * The Cassandra version (2nd line printed when running `cqlsh`) * A stack trace from the error, if there is one From bee6603aa91d424895edb2087a9cbf045b6230b5 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 17 Oct 2016 19:28:20 -0700 Subject: [PATCH 153/196] RUBY-266 - expose execution profiles through cluster object * Also bump version to 3.1.0.rc.1 in preparation of 3.1.0 release. --- Gemfile.lock | 2 +- lib/cassandra/cluster.rb | 6 ++++++ lib/cassandra/execution/profile_manager.rb | 17 ++++++++++++----- lib/cassandra/version.rb | 2 +- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index dfe8bb361..97274895a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - cassandra-driver (3.0.4.rc.1) + cassandra-driver (3.1.0.rc.1) ione (~> 1.2) GEM diff --git a/lib/cassandra/cluster.rb b/lib/cassandra/cluster.rb index fcf7b5fcd..ca54dc5a4 100644 --- a/lib/cassandra/cluster.rb +++ b/lib/cassandra/cluster.rb @@ -288,6 +288,12 @@ def close close_async.get end + # @param name [String] Name of profile to retrieve + # @return [Cassandra::Execution::Profile] execution profile of the given name. + def execution_profile(name) + @profile_manager.profiles[name] + end + # @private def inspect "#<#{self.class.name}:0x#{object_id.to_s(16)} " \ diff --git a/lib/cassandra/execution/profile_manager.rb b/lib/cassandra/execution/profile_manager.rb index b4437be3f..b5324800c 100644 --- a/lib/cassandra/execution/profile_manager.rb +++ b/lib/cassandra/execution/profile_manager.rb @@ -33,13 +33,11 @@ def initialize(default_profile, profiles) @load_balancing_policies = Set.new @load_balancing_policies << default_profile.load_balancing_policy if default_profile.load_balancing_policy + @profiles = {DEFAULT_EXECUTION_PROFILE => default_profile} - profiles.each_value do |profile| - @load_balancing_policies << profile.load_balancing_policy if profile.load_balancing_policy - profile.merge_from(default_profile) + profiles.each do |name, profile| + add_profile(name, profile) end - - @profiles = profiles.merge({DEFAULT_EXECUTION_PROFILE => default_profile}) end def default_profile @@ -59,6 +57,15 @@ def distance(host) return :ignore end + # NOTE: It's only safe to call add_profile when setting up the cluster object. In particular, + # this is only ok before calling Driver#connect. + # @private + def add_profile(name, profile) + @profiles[name] = profile + @load_balancing_policies << profile.load_balancing_policy if profile.load_balancing_policy + profile.merge_from(default_profile) + end + # @private def inspect "#<#{self.class.name}:0x#{object_id.to_s(16)} " \ diff --git a/lib/cassandra/version.rb b/lib/cassandra/version.rb index 664fa4ab1..05496aaa4 100644 --- a/lib/cassandra/version.rb +++ b/lib/cassandra/version.rb @@ -17,5 +17,5 @@ #++ module Cassandra - VERSION = '3.0.4.rc.1'.freeze + VERSION = '3.1.0.rc.1'.freeze end From 78c483e18cedcf002059a9a9c086455bf9236f5d Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Tue, 18 Oct 2016 04:35:10 -0700 Subject: [PATCH 154/196] RUBY-266 - expose execution profiles through cluster object * Fixed race condition in specs. --- spec/support/stub_io_reactor.rb | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/spec/support/stub_io_reactor.rb b/spec/support/stub_io_reactor.rb index 38cf13a81..2494a1338 100644 --- a/spec/support/stub_io_reactor.rb +++ b/spec/support/stub_io_reactor.rb @@ -17,6 +17,8 @@ #++ class StubIoReactor + include MonitorMixin + class NullObject def method_missing(method, *args, &block) self @@ -385,6 +387,8 @@ def initialize @connections = ::Array.new @timers = ::Array.new @max_conns = ::Hash.new + + mon_initialize end def enable_nodes(ips) @@ -465,21 +469,26 @@ def connect(host, port, options) end def schedule_timer(seconds) - promise = Ione::Promise.new - @timers << Timer.new(promise, Time.now + seconds) - promise.future + synchronize do + promise = Ione::Promise.new + @timers << Timer.new(promise, Time.now + seconds) + promise.future + end end def advance_time(seconds) - @timers.dup.each {|timer| timer.advance(seconds)} - @timers.reject! {|timer| timer.expired?} - + synchronize do + @timers.dup.each { |timer| timer.advance(seconds) } + @timers.reject! { |timer| timer.expired? } + end self end def cancel_timer(timer_future) - @timers.reject! do |timer| - timer.resolves?(timer_future) + synchronize do + @timers.reject! do |timer| + timer.resolves?(timer_future) + end end end end From 3aa55f10740f59544a2f6ffe6a82ef1c2802ac72 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Tue, 18 Oct 2016 08:40:07 -0700 Subject: [PATCH 155/196] RUBY-266 - expose execution profiles through cluster object * Minor tweaks to make generated docs cleaner. --- lib/cassandra/cluster.rb | 15 ++++++++------- lib/cassandra/execution/profile_manager.rb | 1 - 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/cassandra/cluster.rb b/lib/cassandra/cluster.rb index ca54dc5a4..fe86d3ddd 100644 --- a/lib/cassandra/cluster.rb +++ b/lib/cassandra/cluster.rb @@ -161,9 +161,16 @@ def protocol_version @connection_options.protocol_version end + # @param name [String] Name of profile to retrieve + # @return [Cassandra::Execution::Profile] execution profile of the given name + def execution_profile(name) + @profile_manager.profiles[name] + end + # @return [Hash] the collection of execution profiles def execution_profiles - @profile_manager.profiles + # Return a dup of the hash to prevent the user from adding/removing profiles from the profile-manager. + @profile_manager.profiles.dup end # @!method refresh_schema_async @@ -288,12 +295,6 @@ def close close_async.get end - # @param name [String] Name of profile to retrieve - # @return [Cassandra::Execution::Profile] execution profile of the given name. - def execution_profile(name) - @profile_manager.profiles[name] - end - # @private def inspect "#<#{self.class.name}:0x#{object_id.to_s(16)} " \ diff --git a/lib/cassandra/execution/profile_manager.rb b/lib/cassandra/execution/profile_manager.rb index b5324800c..0a9514cb2 100644 --- a/lib/cassandra/execution/profile_manager.rb +++ b/lib/cassandra/execution/profile_manager.rb @@ -59,7 +59,6 @@ def distance(host) # NOTE: It's only safe to call add_profile when setting up the cluster object. In particular, # this is only ok before calling Driver#connect. - # @private def add_profile(name, profile) @profiles[name] = profile @load_balancing_policies << profile.load_balancing_policy if profile.load_balancing_policy From 9bc6bb840a2eba5968faf27373ae315545b4717f Mon Sep 17 00:00:00 2001 From: Kishan Karunaratne Date: Fri, 21 Oct 2016 14:54:21 -0700 Subject: [PATCH 156/196] [RUBY-250] Integration tests for cluster options retrieval --- integration/session_test.rb | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/integration/session_test.rb b/integration/session_test.rb index 3cdb52534..55b507fe7 100644 --- a/integration/session_test.rb +++ b/integration/session_test.rb @@ -838,7 +838,7 @@ def test_can_set_protocol_version_explicitly # # @since 3.0.0 # @jira_ticket RUBY-162 - # @expected_result cluster and object options should be visible in the inspect string + # @expected_result cluster and session options should be visible in the inspect string # # @test_category connection # @@ -872,4 +872,33 @@ def test_cluster_session_inspect assert_match(/@payload=nil/, session_inspect) end + # Test for cluster options retrieval + # + # test_can_retrieve_cluster_options tests that cluster objects' options can be retrieved. It first creates a simple + # cluster object with some options defined. It then inspects this object and verifies that the options set can be + # retrieved. + # + # @since 3.1.0 + # @jira_ticket RUBY-250 + # @expected_result cluster options should be retrieved from the cluster object + # + # @test_category connection + # + def test_can_retrieve_cluster_options + setup_schema + + cluster = Cassandra.cluster(hosts: ['127.0.0.1'], consistency: :quorum) + assert_match(/ruby-driver-.*/, cluster.name) + assert_equal '127.0.0.1', cluster.hosts.first.ip.to_s + assert_equal 9042, cluster.port + assert_includes [1,2,3,4], cluster.protocol_version + refute_nil cluster.keyspaces + + execution_profile = cluster.execution_profiles['__DEFAULT_EXECUTION_PROFILE__'] + refute_nil execution_profile + assert_equal Cassandra::LoadBalancing::Policies::TokenAware, execution_profile.load_balancing_policy.class + assert_equal Cassandra::Retry::Policies::Default, execution_profile.retry_policy.class + assert_equal :quorum, execution_profile.consistency + assert_equal 12, execution_profile.timeout + end end From d8360ecbf15924189ebd1ce15e7ab3575ebe00d6 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Fri, 21 Oct 2016 14:54:52 -0700 Subject: [PATCH 157/196] Lint; mostly code reformatting --- .rubocop.yml | 13 +++ Gemfile | 8 +- Rakefile | 28 ++--- cassandra-driver.gemspec | 6 +- docs.yaml | 2 + lib/cassandra.rb | 52 ++++----- lib/cassandra/cluster/client.rb | 120 +++++++++++--------- lib/cassandra/cluster/connector.rb | 15 ++- lib/cassandra/cluster/control_connection.rb | 17 +-- lib/cassandra/cluster/registry.rb | 3 +- lib/cassandra/cluster/schema/fetchers.rb | 23 ++-- lib/cassandra/column_container.rb | 5 +- lib/cassandra/custom_data.rb | 46 ++++---- lib/cassandra/driver.rb | 7 +- lib/cassandra/execution/options.rb | 21 ++-- lib/cassandra/execution/profile.rb | 31 ++--- lib/cassandra/execution/profile_manager.rb | 4 +- lib/cassandra/keyspace.rb | 2 +- lib/cassandra/protocol/coder.rb | 3 +- lib/cassandra/protocol/v3.rb | 3 +- lib/cassandra/protocol/v4.rb | 3 +- lib/cassandra/session.rb | 4 +- lib/cassandra/types.rb | 36 ++---- lib/cassandra/udt.rb | 9 +- 24 files changed, 240 insertions(+), 221 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 67523226d..76236d69f 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -4,6 +4,13 @@ inherit_from: .rubocop_todo.yml AllCops: TargetRubyVersion: 1.9 + Exclude: + - 'spec/**/*' + - 'benchmarking/**/*' + - 'support/**/*' + - 'integration/**/*' + - 'features/**/*' + - 'tmp/**/*' # We shouldn't rescue Exception! Lint/RescueException: @@ -20,6 +27,12 @@ Metrics/ModuleLength: Metrics/ClassLength: Enabled: false +Metrics/ParameterLists: + Enabled: false + +Style/MultilineBlockLayout: + Enabled: false + Style/MultilineTernaryOperator: Enabled: false diff --git a/Gemfile b/Gemfile index 4f4177bb2..a803540a9 100644 --- a/Gemfile +++ b/Gemfile @@ -2,10 +2,10 @@ source 'https://rubygems.org/' gemspec -gem 'snappy', :group => [:development, :test] -gem 'lz4-ruby', :group => [:development, :test] -gem 'rake-compiler', :group => [:development, :test] -gem 'cliver', :group => [:development, :test] +gem 'snappy', group: [:development, :test] +gem 'lz4-ruby', group: [:development, :test] +gem 'rake-compiler', group: [:development, :test] +gem 'cliver', group: [:development, :test] group :development do platforms :mri_19 do diff --git a/Rakefile b/Rakefile index 93c0a6e72..06c2dec43 100644 --- a/Rakefile +++ b/Rakefile @@ -7,16 +7,16 @@ require 'cucumber/rake/task' require 'rake/testtask' require 'bundler/gem_tasks' -ENV["FAIL_FAST"] ||= 'Y' +ENV['FAIL_FAST'] ||= 'Y' -RSpec::Core::RakeTask.new(:rspec => :compile) +RSpec::Core::RakeTask.new(rspec: :compile) -Cucumber::Rake::Task.new(:cucumber => :compile) +Cucumber::Rake::Task.new(cucumber: :compile) desc 'Run all tests' -task :test => [:rspec, :integration, :cucumber] +task test: [:rspec, :integration, :cucumber] -ruby_engine = defined?(RUBY_ENGINE)? RUBY_ENGINE : 'ruby' +ruby_engine = defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby' case ruby_engine when 'jruby' @@ -29,19 +29,19 @@ else Rake::ExtensionTask.new('cassandra_murmur3') end -Rake::TestTask.new(:integration => :compile) do |t| - t.libs.push "lib" +Rake::TestTask.new(integration: :compile) do |t| + t.libs.push 'lib' t.test_files = FileList['integration/*_test.rb', - 'integration/security/*_test.rb', - 'integration/load_balancing/*_test.rb', - 'integration/types/*_test.rb', - 'integration/functions/*_test.rb', - 'integration/indexes/*_test.rb'] + 'integration/security/*_test.rb', + 'integration/load_balancing/*_test.rb', + 'integration/types/*_test.rb', + 'integration/functions/*_test.rb', + 'integration/indexes/*_test.rb'] t.verbose = true end -Rake::TestTask.new(:stress => :compile) do |t| - t.libs.push "lib" +Rake::TestTask.new(stress: :compile) do |t| + t.libs.push 'lib' t.test_files = FileList['integration/stress_tests/*_test.rb'] t.verbose = true end diff --git a/cassandra-driver.gemspec b/cassandra-driver.gemspec index e5f84a8be..45002f1e9 100644 --- a/cassandra-driver.gemspec +++ b/cassandra-driver.gemspec @@ -1,6 +1,6 @@ # encoding: utf-8 -$: << File.expand_path('../lib', __FILE__) +$LOAD_PATH << File.expand_path('../lib', __FILE__) require 'cassandra/version' @@ -10,8 +10,8 @@ Gem::Specification.new do |s| s.authors = ['Theo Hultberg', 'Bulat Shakirzyanov', 'Sandeep Tamhankar'] s.email = ['theo@iconara.net', 'bulat.shakirzyanov@datastax.com', 'sandeep.tamhankar@datastax.com'] s.homepage = 'http://datastax.github.io/ruby-driver' - s.summary = %q{Datastax Ruby Driver for Apache Cassandra} - s.description = %q{A pure Ruby driver for Apache Cassandra} + s.summary = 'Datastax Ruby Driver for Apache Cassandra' + s.description = 'A pure Ruby driver for Apache Cassandra' s.license = 'Apache License 2.0' s.files = Dir['lib/**/*.rb', 'README.md', '.yardopts'] s.require_paths = %w(lib) diff --git a/docs.yaml b/docs.yaml index dbd646b0d..706f5e07a 100644 --- a/docs.yaml +++ b/docs.yaml @@ -23,6 +23,8 @@ rewrites: replace: http://docs.datastax.com/en/cql/3.3/cql/cql_using/useAboutCQL.html - search: https://datastax.github.io/java-driver/features replace: https://datastax.github.io/java-driver/manual + - search: http://dsdocs30_java + replace: http://docs.datastax.com/en/developer/java-driver/3.0 links: - title: Code href: https://github.com/datastax/ruby-driver/ diff --git a/lib/cassandra.rb b/lib/cassandra.rb index d4f59d72c..c85aa6dae 100644 --- a/lib/cassandra.rb +++ b/lib/cassandra.rb @@ -528,12 +528,11 @@ def self.validate_and_massage_options(options) if options.key?(:execution_profiles) options[:execution_profiles].each do |name, profile| timeout = profile.timeout - unless timeout.nil? - Util.assert_instance_of(::Numeric, timeout, - ":timeout of execution profile #{name} must be a number of seconds, " \ - "#{timeout.inspect} given") - Util.assert(timeout > 0, ":timeout of execution profile #{name} must be greater than 0, #{timeout} given") - end + next if timeout.nil? + Util.assert_instance_of(::Numeric, timeout, + ":timeout of execution profile #{name} must be a number of seconds, " \ + "#{timeout.inspect} given") + Util.assert(timeout > 0, ":timeout of execution profile #{name} must be greater than 0, #{timeout} given") end end @@ -599,12 +598,11 @@ def self.validate_and_massage_options(options) :distance, :plan] options[:execution_profiles].each do |name, profile| load_balancing_policy = profile.load_balancing_policy - unless load_balancing_policy.nil? - Util.assert_responds_to_all(methods, load_balancing_policy, - ":load_balancing_policy in execution profile #{name} #{load_balancing_policy.inspect} must respond " \ - "to #{methods.inspect}, but doesn't" - ) - end + next if load_balancing_policy.nil? + Util.assert_responds_to_all(methods, load_balancing_policy, + ":load_balancing_policy in execution profile #{name} " \ + "#{load_balancing_policy.inspect} must respond " \ + "to #{methods.inspect}, but doesn't") end end @@ -629,12 +627,10 @@ def self.validate_and_massage_options(options) methods = [:read_timeout, :write_timeout, :unavailable] options[:execution_profiles].each do |name, profile| retry_policy = profile.retry_policy - unless retry_policy.nil? - Util.assert_responds_to_all(methods, retry_policy, - ":retry_policy in execution profile #{name} #{retry_policy.inspect} must " \ - "respond to #{methods.inspect}, but doesn't" - ) - end + next if retry_policy.nil? + Util.assert_responds_to_all(methods, retry_policy, + ":retry_policy in execution profile #{name} #{retry_policy.inspect} must " \ + "respond to #{methods.inspect}, but doesn't") end end @@ -643,19 +639,16 @@ def self.validate_and_massage_options(options) if options.key?(:consistency) consistency = options[:consistency] Util.assert_one_of(CONSISTENCIES, consistency, - ":consistency must be one of #{CONSISTENCIES.inspect}, " \ - "#{consistency.inspect} given" - ) + ":consistency must be one of #{CONSISTENCIES.inspect}, " \ + "#{consistency.inspect} given") end if options.key?(:execution_profiles) options[:execution_profiles].each do |name, profile| consistency = profile.consistency - unless consistency.nil? - Util.assert_one_of(CONSISTENCIES, consistency, - ":consistency in execution profile #{name} must be one of #{CONSISTENCIES.inspect}, " \ - "#{consistency.inspect} given" - ) - end + next if consistency.nil? + Util.assert_one_of(CONSISTENCIES, consistency, + ":consistency in execution profile #{name} must be one of #{CONSISTENCIES.inspect}, " \ + "#{consistency.inspect} given") end end @@ -681,9 +674,8 @@ def self.validate_and_massage_options(options) unless protocol_version.nil? Util.assert_instance_of(::Integer, protocol_version) Util.assert_one_of(1..Cassandra::Protocol::Versions::MAX_SUPPORTED_VERSION, protocol_version, - ':protocol_version must be a positive integer between 1 and ' \ - "#{Cassandra::Protocol::Versions::MAX_SUPPORTED_VERSION}, #{protocol_version.inspect} given" - ) + ':protocol_version must be a positive integer between 1 and ' \ + "#{Cassandra::Protocol::Versions::MAX_SUPPORTED_VERSION}, #{protocol_version.inspect} given") end end diff --git a/lib/cassandra/cluster/client.rb b/lib/cassandra/cluster/client.rb index a5b421022..e96e56aba 100644 --- a/lib/cassandra/cluster/client.rb +++ b/lib/cassandra/cluster/client.rb @@ -222,7 +222,9 @@ def query(statement, options) return @futures.error( Errors::ClientError.new( 'Positional arguments are not supported by the current version of ' \ - 'Apache Cassandra')) + 'Apache Cassandra' + ) + ) end timestamp = @timestamp_generator.next if @timestamp_generator && @connection_options.protocol_version > 2 @@ -310,7 +312,9 @@ def batch(statement, options) return @futures.error( Errors::ClientError.new( 'Batch statements are not supported by the current version of ' \ - 'Apache Cassandra')) + 'Apache Cassandra' + ) + ) end timestamp = @timestamp_generator.next if @timestamp_generator && @connection_options.protocol_version > 2 @@ -361,13 +365,15 @@ def inspect 'SELECT peer, rpc_address, schema_version FROM system.peers', EMPTY_LIST, EMPTY_LIST, - :one) + :one + ) SELECT_SCHEMA_LOCAL = Protocol::QueryRequest.new( "SELECT schema_version FROM system.local WHERE key='local'", EMPTY_LIST, EMPTY_LIST, - :one) + :one + ) def connected(f) if f.resolved? @@ -1072,24 +1078,24 @@ def handle_response(response_future, case r when Protocol::UnavailableErrorResponse decision = options.retry_policy.unavailable(statement, - r.consistency, - r.required, - r.alive, - retries) + r.consistency, + r.required, + r.alive, + retries) when Protocol::WriteTimeoutErrorResponse decision = options.retry_policy.write_timeout(statement, - r.consistency, - r.write_type, - r.blockfor, - r.received, - retries) + r.consistency, + r.write_type, + r.blockfor, + r.received, + retries) when Protocol::ReadTimeoutErrorResponse decision = options.retry_policy.read_timeout(statement, - r.consistency, - r.blockfor, - r.received, - r.data_present, - retries) + r.consistency, + r.blockfor, + r.received, + r.data_present, + retries) when Protocol::UnpreparedErrorResponse cql = nil if statement.is_a?(Cassandra::Statements::Batch) @@ -1231,7 +1237,8 @@ def handle_response(response_future, request.consistency, retries, self, - @connection_options)) + @connection_options) + ) when Protocol::RawRowsResultResponse r.materialize(statement.result_metadata) promise.fulfill( @@ -1247,7 +1254,8 @@ def handle_response(response_future, request.consistency, retries, self, - @futures)) + @futures) + ) when Protocol::RowsResultResponse promise.fulfill( Results::Paged.new(r.custom_payload, @@ -1262,7 +1270,8 @@ def handle_response(response_future, request.consistency, retries, self, - @futures)) + @futures) + ) when Protocol::SchemaChangeResultResponse if r.change == 'DROPPED' && r.target == Protocol::Constants::SCHEMA_CHANGE_TARGET_KEYSPACE @@ -1275,7 +1284,8 @@ def handle_response(response_future, unless f.resolved? f.on_failure do |e| @logger.error( - "Schema agreement failure (#{e.class.name}: #{e.message})") + "Schema agreement failure (#{e.class.name}: #{e.message})" + ) end end promise.fulfill( @@ -1289,7 +1299,8 @@ def handle_response(response_future, request.consistency, retries, self, - @futures)) + @futures) + ) end else promise.fulfill(Results::Void.new(r.custom_payload, @@ -1377,7 +1388,8 @@ def handle_response(response_future, request.consistency, retries, self, - @futures)) + @futures) + ) when Retry::Decisions::Reraise promise.break( r.to_error(keyspace, @@ -1385,7 +1397,8 @@ def handle_response(response_future, options, hosts, request.consistency, - retries)) + retries) + ) else promise.break( r.to_error(keyspace, @@ -1393,7 +1406,8 @@ def handle_response(response_future, options, hosts, request.consistency, - retries)) + retries) + ) end end rescue => e @@ -1402,34 +1416,23 @@ def handle_response(response_future, else response_future.on_failure do |ex| if ex.is_a?(Errors::HostError) || - (ex.is_a?(Errors::TimeoutError) && statement.idempotent?) + (ex.is_a?(Errors::TimeoutError) && statement.idempotent?) errors[host] = ex case request - when Protocol::QueryRequest, Protocol::PrepareRequest - send_request_by_plan(promise, - keyspace, - statement, - options, - request, - plan, - timeout, - errors, - hosts, - retries) - when Protocol::ExecuteRequest - execute_by_plan(promise, - keyspace, - statement, - options, - request, - plan, - timeout, - errors, - hosts, - retries) - when Protocol::BatchRequest - batch_by_plan(promise, + when Protocol::QueryRequest, Protocol::PrepareRequest + send_request_by_plan(promise, + keyspace, + statement, + options, + request, + plan, + timeout, + errors, + hosts, + retries) + when Protocol::ExecuteRequest + execute_by_plan(promise, keyspace, statement, options, @@ -1439,8 +1442,19 @@ def handle_response(response_future, errors, hosts, retries) - else - promise.break(ex) + when Protocol::BatchRequest + batch_by_plan(promise, + keyspace, + statement, + options, + request, + plan, + timeout, + errors, + hosts, + retries) + else + promise.break(ex) end else promise.break(ex) diff --git a/lib/cassandra/cluster/connector.rb b/lib/cassandra/cluster/connector.rb index dea54563f..dc446d2f5 100644 --- a/lib/cassandra/cluster/connector.rb +++ b/lib/cassandra/cluster/connector.rb @@ -222,10 +222,12 @@ def startup_connection(host, connection, cql_version, compression) ::Ione::Future.resolved(connection) when Protocol::ErrorResponse ::Ione::Future.failed( - r.to_error(nil, VOID_STATEMENT, VOID_OPTIONS, EMPTY_LIST, :one, 0)) + r.to_error(nil, VOID_STATEMENT, VOID_OPTIONS, EMPTY_LIST, :one, 0) + ) else ::Ione::Future.failed( - Errors::InternalError.new("Unexpected response #{r.inspect}")) + Errors::InternalError.new("Unexpected response #{r.inspect}") + ) end end end @@ -241,7 +243,8 @@ def cannot_authenticate_error VOID_OPTIONS, EMPTY_LIST, :one, - 0) + 0 + ) end def request_options(connection) @@ -288,10 +291,12 @@ def challenge_response_cycle(connection, authenticator, token) ::Ione::Future.resolved(connection) when Protocol::ErrorResponse ::Ione::Future.failed( - r.to_error(nil, VOID_STATEMENT, VOID_OPTIONS, EMPTY_LIST, :one, 0)) + r.to_error(nil, VOID_STATEMENT, VOID_OPTIONS, EMPTY_LIST, :one, 0) + ) else ::Ione::Future.failed( - Errors::InternalError.new("Unexpected response #{r.inspect}")) + Errors::InternalError.new("Unexpected response #{r.inspect}") + ) end end end diff --git a/lib/cassandra/cluster/control_connection.rb b/lib/cassandra/cluster/control_connection.rb index 6179a8da4..f33e31ab2 100644 --- a/lib/cassandra/cluster/control_connection.rb +++ b/lib/cassandra/cluster/control_connection.rb @@ -90,7 +90,8 @@ def host_up(host) (@status == :closing || @status == :closed) || @load_balancing_policy.distance(host) == :ignore return connect_to_first_available( - @load_balancing_policy.plan(nil, VOID_STATEMENT, VOID_OPTIONS)) + @load_balancing_policy.plan(nil, VOID_STATEMENT, VOID_OPTIONS) + ) end end @@ -148,18 +149,20 @@ def inspect private - SELECT_LOCAL = Protocol::QueryRequest.new( + SELECT_LOCAL = Protocol::QueryRequest.new( 'SELECT * ' \ 'FROM system.local', EMPTY_LIST, EMPTY_LIST, - :one) - SELECT_PEERS = Protocol::QueryRequest.new( + :one + ) + SELECT_PEERS = Protocol::QueryRequest.new( 'SELECT * ' \ 'FROM system.peers', EMPTY_LIST, EMPTY_LIST, - :one) + :one + ) SELECT_PEER_QUERY = 'SELECT * ' \ @@ -677,7 +680,7 @@ def peer_ip(data, host_address) rpc_address = data['rpc_address'] if rpc_address.nil? - @logger.info("The system.peers row for '#{data['peer']}' has no rpc_address. This is likely " + + @logger.info("The system.peers row for '#{data['peer']}' has no rpc_address. This is likely " \ 'a gossip or snitch issue. This host will be ignored.') return nil end @@ -686,7 +689,7 @@ def peer_ip(data, host_address) # Some DSE versions were inserting a line for the local node in peers (with mostly null values). # This has been fixed, but if we detect that's the case, ignore it as it's not really a big deal. - @logger.debug("System.peers on node #{host_address} has a line for itself. This is not normal but is a " + + @logger.debug("System.peers on node #{host_address} has a line for itself. This is not normal but is a " \ 'known problem of some DSE versions. Ignoring the entry.') return nil end diff --git a/lib/cassandra/cluster/registry.rb b/lib/cassandra/cluster/registry.rb index 41962587f..a77abf2da 100644 --- a/lib/cassandra/cluster/registry.rb +++ b/lib/cassandra/cluster/registry.rb @@ -170,8 +170,7 @@ def create_host(ip, data) Array(data['tokens']).freeze, :up, data['broadcast_address'], - data['listen_address'] - ) + data['listen_address']) end def toggle_up(host) diff --git a/lib/cassandra/cluster/schema/fetchers.rb b/lib/cassandra/cluster/schema/fetchers.rb index a169365d2..6412722ea 100644 --- a/lib/cassandra/cluster/schema/fetchers.rb +++ b/lib/cassandra/cluster/schema/fetchers.rb @@ -36,8 +36,7 @@ def fetch(connection) select_materialized_views(connection), select_indexes(connection), select_triggers(connection)) - .map do |rows_keyspaces, rows_tables, rows_columns, rows_types, rows_functions, rows_aggregates, - rows_views, rows_indexes, rows_triggers| + .map do |rows_keyspaces, rows_tables, rows_columns, rows_types, rows_functions, rows_aggregates, rows_views, rows_indexes, rows_triggers| lookup_tables = map_rows_by(rows_tables, 'keyspace_name') lookup_columns = map_rows_by(rows_columns, 'keyspace_name') lookup_types = map_rows_by(rows_types, 'keyspace_name') @@ -263,7 +262,8 @@ def select_aggregate(connection, keyspace_name, aggregate_name, aggregate_args) def send_select_request(connection, cql, params = EMPTY_LIST, types = EMPTY_LIST) backtrace = caller connection.send_request( - Protocol::QueryRequest.new(cql, params, types, :one)).map do |r| + Protocol::QueryRequest.new(cql, params, types, :one) + ).map do |r| case r when Protocol::RowsResultResponse r.rows @@ -509,7 +509,8 @@ def create_index(table, column, row_column) # Most of this logic was taken from the Java driver. options = {} # For some versions of C*, this field could have a literal string 'null' value. - if !row_column['index_options'].nil? && row_column['index_options'] != 'null' && !row_column['index_options'].empty? + if !row_column['index_options'].nil? && row_column['index_options'] != 'null' && + !row_column['index_options'].empty? options = ::JSON.load(row_column['index_options']) end column_name = Util.escape_name(column.name) @@ -579,7 +580,7 @@ def create_table_options(table_data, compaction_strategy, is_compact) end class V2_0_x < V1_2_x - SELECT_TRIGGERS = 'SELECT * FROM system.schema_triggers'.freeze + SELECT_TRIGGERS = 'SELECT * FROM system.schema_triggers'.freeze SELECT_KEYSPACE = 'SELECT * FROM system.schema_keyspaces WHERE keyspace_name = ?'.freeze @@ -587,8 +588,8 @@ class V2_0_x < V1_2_x 'SELECT * FROM system.schema_columnfamilies WHERE keyspace_name = ?'.freeze SELECT_KEYSPACE_COLUMNS = 'SELECT * FROM system.schema_columns WHERE keyspace_name = ?'.freeze - SELECT_KEYSPACE_TRIGGERS = - 'SELECT * FROM system.schema_triggers WHERE keyspace_name = ?'.freeze + SELECT_KEYSPACE_TRIGGERS = + 'SELECT * FROM system.schema_triggers WHERE keyspace_name = ?'.freeze SELECT_TABLE = 'SELECT * ' \ @@ -598,7 +599,7 @@ class V2_0_x < V1_2_x 'SELECT * ' \ 'FROM system.schema_columns ' \ 'WHERE keyspace_name = ? AND columnfamily_name = ?'.freeze - SELECT_TABLE_TRIGGERS = + SELECT_TABLE_TRIGGERS = 'SELECT * ' \ 'FROM system.schema_triggers ' \ 'WHERE keyspace_name = ? AND columnfamily_name = ?'.freeze @@ -894,7 +895,9 @@ def create_aggregate(aggregate_data, functions) initial_state = Util.encode_object( Protocol::Coder.read_value_v4( Protocol::CqlByteBuffer.new.append_bytes(aggregate_data['initcond']), - state_type, nil)) + state_type, nil + ) + ) # The state-function takes arguments: first the stype, then the args of the aggregate. state_function = functions.get(aggregate_data['state_func'], @@ -989,7 +992,7 @@ class V3_0_x < V2_2_x 'SELECT * ' \ 'FROM system_schema.indexes ' \ 'WHERE keyspace_name = ? AND table_name = ?'.freeze - SELECT_TABLE_TRIGGERS = + SELECT_TABLE_TRIGGERS = 'SELECT * ' \ 'FROM system_schema.triggers ' \ 'WHERE keyspace_name = ? AND table_name = ?'.freeze diff --git a/lib/cassandra/column_container.rb b/lib/cassandra/column_container.rb index f81763957..1ce5b3c1f 100644 --- a/lib/cassandra/column_container.rb +++ b/lib/cassandra/column_container.rb @@ -64,7 +64,6 @@ class Options attr_reader :cdc # @private - # rubocop:disable Metrics/ParameterLists def initialize(comment, read_repair_chance, local_read_repair_chance, @@ -135,7 +134,7 @@ def to_cql options << "bloom_filter_fp_chance = #{Util.encode_object(@bloom_filter_fp_chance)}" end options << "caching = #{Util.encode_object(@caching)}" unless @caching.nil? - options << "cdc = true" if @cdc + options << 'cdc = true' if @cdc options << "comment = #{Util.encode_object(@comment)}" unless @comment.nil? options << "compaction = #{@compaction_strategy.to_cql}" unless @compaction_strategy.nil? options << "compression = #{Util.encode_object(@compression)}" unless @compression.nil? @@ -181,7 +180,7 @@ def eql?(other) @compression == other.compression && @compact_storage == other.compact_storage? && @crc_check_chance == other.crc_check_chance && - @extensions == other.extensions + @extensions == other.extensions && @cdc == other.cdc end alias == eql? diff --git a/lib/cassandra/custom_data.rb b/lib/cassandra/custom_data.rb index dad1266a6..25de823a8 100644 --- a/lib/cassandra/custom_data.rb +++ b/lib/cassandra/custom_data.rb @@ -20,32 +20,34 @@ # columns in C*. This module has no logic of its own, but indicates that the marked class has # certain methods. # @private -module Cassandra::CustomData - def self.included base - base.send :include, InstanceMethods - base.extend ClassMethods - end - - module ClassMethods - # @return [Cassandra::Types::Custom] the custom type that this class represents. - def type - raise NotImplementedError, "#{self.class} must implement the :type class method" +module Cassandra + module CustomData + def self.included(base) + base.send :include, InstanceMethods + base.extend ClassMethods end - # Deserialize the given data into an instance of this domain object class. - # @param data [String] byte-array representation of a column value of this custom type. - # @return An instance of the domain object class. - # @raise [Cassandra::Errors::DecodingError] upon failure. - def deserialize(data) - raise NotImplementedError, "#{self.class} must implement the :deserialize class method" + module ClassMethods + # @return [Cassandra::Types::Custom] the custom type that this class represents. + def type + raise NotImplementedError, "#{self.class} must implement the :type class method" + end + + # Deserialize the given data into an instance of this domain object class. + # @param data [String] byte-array representation of a column value of this custom type. + # @return An instance of the domain object class. + # @raise [Cassandra::Errors::DecodingError] upon failure. + def deserialize(data) + raise NotImplementedError, "#{self.class} must implement the :deserialize class method" + end end - end - module InstanceMethods - # Serialize this domain object into a byte array to send to C*. - # @return [String] byte-array representation of this domain object. - def serialize - raise NotImplementedError, "#{self.class} must implement the :serialize instance method" + module InstanceMethods + # Serialize this domain object into a byte array to send to C*. + # @return [String] byte-array representation of this domain object. + def serialize + raise NotImplementedError, "#{self.class} must implement the :serialize instance method" + end end end end diff --git a/lib/cassandra/driver.rb b/lib/cassandra/driver.rb index 8e80192bd..42ba0881d 100644 --- a/lib/cassandra/driver.rb +++ b/lib/cassandra/driver.rb @@ -109,7 +109,7 @@ def self.let(name, &block) address_resolution_policy, connector, futures_factory, - timestamp_generator) + timestamp_generator) end let(:execution_options) do @@ -146,7 +146,7 @@ def self.let(name, &block) ) end - let(:custom_types) { [] } + let(:custom_types) { [] } let(:port) { 9042 } let(:protocol_version) { nil } let(:allow_beta_protocol) { false } @@ -160,7 +160,8 @@ def self.let(name, &block) let(:load_balancing_policy) do LoadBalancing::Policies::TokenAware.new( LoadBalancing::Policies::DCAwareRoundRobin.new(datacenter, 0), - shuffle_replicas) + shuffle_replicas + ) end let(:reconnection_policy) do Reconnection::Policies::Exponential.new(0.5, 30, 2) diff --git a/lib/cassandra/execution/options.rb b/lib/cassandra/execution/options.rb index 81a4a9ca7..372445eab 100644 --- a/lib/cassandra/execution/options.rb +++ b/lib/cassandra/execution/options.rb @@ -50,11 +50,11 @@ class Options attr_reader :paging_state # @return [nil, Hash] custom outgoing payload, a map of - # string and byte buffers. + # string and byte buffers. # # @see https://github.com/apache/cassandra/blob/cassandra-3.4/doc/native_protocol_v4.spec#L125-L131 Description # of custom payload in Cassandra native protocol v4. - # @see http://docs.datastax.com/en/developer/java-driver/3.0/manual/custom_payloads/#enabling-custom-payloads-on-c-nodes + # @see http://dsdocs30_java/manual/custom_payloads/#enabling-custom-payloads-on-c-nodes # Enabling custom payloads on Cassandra nodes. # # @example Sending a custom payload @@ -64,11 +64,12 @@ class Options # }) attr_reader :payload - # @return [Cassandra::LoadBalancing::Policy] load-balancing policy that determines which node will run the next statement. + # @return [Cassandra::LoadBalancing::Policy] load-balancing policy that determines which node will run the + # next statement. attr_reader :load_balancing_policy - # @return [Cassandra::Retry::Policy] retry policy that determines how request retries should be handled for different - # failure modes. + # @return [Cassandra::Retry::Policy] retry policy that determines how request retries should be handled for + # different failure modes. attr_reader :retry_policy # @private @@ -106,16 +107,14 @@ def initialize(options, trusted_options = nil) methods = [:host_up, :host_down, :host_found, :host_lost, :setup, :teardown, :distance, :plan] Util.assert_responds_to_all(methods, load_balancing_policy, - ":load_balancing_policy #{load_balancing_policy.inspect} must respond " \ - "to #{methods.inspect}, but doesn't" - ) + ":load_balancing_policy #{load_balancing_policy.inspect} must respond " \ + "to #{methods.inspect}, but doesn't") end if trusted_options.nil? || !retry_policy.nil? methods = [:read_timeout, :write_timeout, :unavailable] Util.assert_responds_to_all(methods, retry_policy, - ":retry_policy #{retry_policy.inspect} must respond to #{methods.inspect}, " \ - "but doesn't" - ) + ":retry_policy #{retry_policy.inspect} must respond to #{methods.inspect}, " \ + "but doesn't") end unless serial_consistency.nil? diff --git a/lib/cassandra/execution/profile.rb b/lib/cassandra/execution/profile.rb index a55d034e3..00d6dcba3 100644 --- a/lib/cassandra/execution/profile.rb +++ b/lib/cassandra/execution/profile.rb @@ -28,11 +28,12 @@ module Execution # @see Session#prepare_async # @see Session#prepare class Profile - # @return [Cassandra::LoadBalancing::Policy] load-balancing policy that determines which node will run the next statement. + # @return [Cassandra::LoadBalancing::Policy] load-balancing policy that determines which node will run the + # next statement. attr_reader :load_balancing_policy - # @return [Cassandra::Retry::Policy] retry policy that determines how request retries should be handled for different - # failure modes. + # @return [Cassandra::Retry::Policy] retry policy that determines how request retries should be handled for + # different failure modes. attr_reader :retry_policy # @return [Symbol] consistency level with which to run statements. @@ -42,10 +43,10 @@ class Profile attr_reader :timeout # @private - DEFAULT_OPTIONS={load_balancing_policy: nil, - retry_policy: nil, - consistency: nil, - timeout: :unspecified} + DEFAULT_OPTIONS = {load_balancing_policy: nil, + retry_policy: nil, + consistency: nil, + timeout: :unspecified}.freeze # @private DEFAULT_TIMEOUT = 12 @@ -75,20 +76,20 @@ def timeout # @private def to_h { - load_balancing_policy: @load_balancing_policy, - retry_policy: @retry_policy, - consistency: @consistency, - timeout: timeout + load_balancing_policy: @load_balancing_policy, + retry_policy: @retry_policy, + consistency: @consistency, + timeout: timeout } end # @private def eql?(other) other.is_a?(Profile) && \ - @load_balancing_policy == other.load_balancing_policy && \ - @retry_policy == other.retry_policy && \ - @consistency == other.consistency && \ - @timeout == other.instance_variable_get(:@timeout) + @load_balancing_policy == other.load_balancing_policy && \ + @retry_policy == other.retry_policy && \ + @consistency == other.consistency && \ + @timeout == other.instance_variable_get(:@timeout) end alias == eql? diff --git a/lib/cassandra/execution/profile_manager.rb b/lib/cassandra/execution/profile_manager.rb index 0a9514cb2..ab17803eb 100644 --- a/lib/cassandra/execution/profile_manager.rb +++ b/lib/cassandra/execution/profile_manager.rb @@ -25,7 +25,7 @@ class ProfileManager # Name of the default execution profile. Use this constant as the key for an execution profile when initializing # a {Cluster} to override the default execution profile with your own. - DEFAULT_EXECUTION_PROFILE = '__DEFAULT_EXECUTION_PROFILE__' + DEFAULT_EXECUTION_PROFILE = '__DEFAULT_EXECUTION_PROFILE__'.freeze def initialize(default_profile, profiles) # Walk through the profiles and fill them out with attributes from the default profile when they're not @@ -54,7 +54,7 @@ def distance(host) return :remote if distances.include?(:remote) # Fall back to ignore the host. - return :ignore + :ignore end # NOTE: It's only safe to call add_profile when setting up the cluster object. In particular, diff --git a/lib/cassandra/keyspace.rb b/lib/cassandra/keyspace.rb index 751746bc8..348250407 100644 --- a/lib/cassandra/keyspace.rb +++ b/lib/cassandra/keyspace.rb @@ -168,7 +168,7 @@ def materialized_view(name) def each_materialized_view(&block) if block_given? @views.each_value do |v| - block.call(v) if v.base_table + yield(v) if v.base_table end self else diff --git a/lib/cassandra/protocol/coder.rb b/lib/cassandra/protocol/coder.rb index e79dc18c5..13d8b6e42 100644 --- a/lib/cassandra/protocol/coder.rb +++ b/lib/cassandra/protocol/coder.rb @@ -268,7 +268,8 @@ def read_value_v4(buffer, type, custom_type_handlers) value = ::Hash.new buffer.read_signed_int.times do - value[read_value_v4(buffer, key_type, custom_type_handlers)] = read_value_v4(buffer, value_type, custom_type_handlers) + value[read_value_v4(buffer, key_type, custom_type_handlers)] = + read_value_v4(buffer, value_type, custom_type_handlers) end value diff --git a/lib/cassandra/protocol/v3.rb b/lib/cassandra/protocol/v3.rb index 8409f06b7..2874eaf20 100644 --- a/lib/cassandra/protocol/v3.rb +++ b/lib/cassandra/protocol/v3.rb @@ -166,7 +166,8 @@ def actual_decode(buffer, fields, frame_length, code) if compression == 1 if @compressor buffer = CqlByteBuffer.new( - @compressor.decompress(buffer.read(frame_length))) + @compressor.decompress(buffer.read(frame_length)) + ) frame_length = buffer.size else raise Errors::DecodingError, diff --git a/lib/cassandra/protocol/v4.rb b/lib/cassandra/protocol/v4.rb index 3aa95cad6..754e18501 100644 --- a/lib/cassandra/protocol/v4.rb +++ b/lib/cassandra/protocol/v4.rb @@ -179,7 +179,8 @@ def actual_decode(buffer, fields, frame_length, code) if compression if @compressor buffer = CqlByteBuffer.new( - @compressor.decompress(buffer.read(frame_length))) + @compressor.decompress(buffer.read(frame_length)) + ) frame_length = buffer.size else raise Errors::DecodingError, diff --git a/lib/cassandra/session.rb b/lib/cassandra/session.rb index fd666c30d..131ca0c07 100644 --- a/lib/cassandra/session.rb +++ b/lib/cassandra/session.rb @@ -248,9 +248,7 @@ def merge_execution_options(options) execution_profile = nil if options.key?(:execution_profile) execution_profile = @profile_manager.profiles[options[:execution_profile]] - unless execution_profile - raise ::ArgumentError.new("Unknown execution profile #{options[:execution_profile]}") - end + raise ::ArgumentError.new("Unknown execution profile #{options[:execution_profile]}") unless execution_profile end # This looks a little hokey, so let's explain: Execution::Options.override takes a diff --git a/lib/cassandra/types.rb b/lib/cassandra/types.rb index 82fe0f819..4048ea868 100644 --- a/lib/cassandra/types.rb +++ b/lib/cassandra/types.rb @@ -1518,8 +1518,7 @@ def tinyint # @return [Cassandra::Types::List] list type def list(value_type) Util.assert_instance_of(Cassandra::Type, value_type, - "list type must be a Cassandra::Type, #{value_type.inspect} given" - ) + "list type must be a Cassandra::Type, #{value_type.inspect} given") List.new(value_type) end @@ -1529,11 +1528,9 @@ def list(value_type) # @return [Cassandra::Types::Map] map type def map(key_type, value_type) Util.assert_instance_of(Cassandra::Type, key_type, - "map key type must be a Cassandra::Type, #{key_type.inspect} given" - ) + "map key type must be a Cassandra::Type, #{key_type.inspect} given") Util.assert_instance_of(Cassandra::Type, value_type, - "map value type must be a Cassandra::Type, #{value_type.inspect} given" - ) + "map value type must be a Cassandra::Type, #{value_type.inspect} given") Map.new(key_type, value_type) end @@ -1542,8 +1539,7 @@ def map(key_type, value_type) # @return [Cassandra::Types::Set] set type def set(value_type) Util.assert_instance_of(Cassandra::Type, value_type, - "set type must be a Cassandra::Type, #{value_type.inspect} given" - ) + "set type must be a Cassandra::Type, #{value_type.inspect} given") Set.new(value_type) end @@ -1555,8 +1551,7 @@ def tuple(*members) members.each do |member| Util.assert_instance_of(Cassandra::Type, member, 'each tuple member must be a Cassandra::Type, ' \ - "#{member.inspect} given" - ) + "#{member.inspect} given") end Tuple.new(*members) @@ -1598,40 +1593,33 @@ def udt(keyspace, name, *fields) fields = Array(fields.first) if fields.one? Util.assert_not_empty(fields, - 'user-defined type must contain at least one field' - ) + 'user-defined type must contain at least one field') if fields.first.is_a?(::Array) fields = fields.map do |pair| Util.assert(pair.size == 2, 'fields of a user-defined type must be an Array of name and ' \ - "value pairs, #{pair.inspect} given" - ) + "value pairs, #{pair.inspect} given") Util.assert_instance_of(::String, pair[0], 'each field name for a user-defined type must be a String, ' \ - "#{pair[0].inspect} given" - ) + "#{pair[0].inspect} given") Util.assert_instance_of(Cassandra::Type, pair[1], 'each field type for a user-defined type must be a ' \ - "Cassandra::Type, #{pair[1].inspect} given" - ) + "Cassandra::Type, #{pair[1].inspect} given") UserDefined::Field.new(*pair) end else Util.assert(fields.size.even?, 'fields of a user-defined type must be an Array of alternating ' \ - "names and values pairs, #{fields.inspect} given" - ) + "names and values pairs, #{fields.inspect} given") fields = fields.each_slice(2).map do |field_name, field_type| Util.assert_instance_of(::String, field_name, 'each field name for a user-defined type must be a String, ' \ - "#{field_name.inspect} given" - ) + "#{field_name.inspect} given") Util.assert_instance_of(Cassandra::Type, field_type, 'each field type for a user-defined type must be a ' \ - "Cassandra::Type, #{field_type.inspect} given" - ) + "Cassandra::Type, #{field_type.inspect} given") UserDefined::Field.new(field_name, field_type) end diff --git a/lib/cassandra/udt.rb b/lib/cassandra/udt.rb index 0d9b777d5..f7d129bd4 100644 --- a/lib/cassandra/udt.rb +++ b/lib/cassandra/udt.rb @@ -224,15 +224,13 @@ def initialize(*values) values = Array(values.first) if values.one? Util.assert_not_empty(values, - 'user-defined type must contain at least one value' - ) + 'user-defined type must contain at least one value') if values.first.is_a?(::Array) @values = values.map do |pair| Util.assert(pair.size == 2, 'values of a user-defined type must be an Array of name and ' \ - "value pairs, #{pair.inspect} given" - ) + "value pairs, #{pair.inspect} given") name, value = pair [String(name), value] @@ -240,8 +238,7 @@ def initialize(*values) else Util.assert(values.size.even?, 'values of a user-defined type must be an Array of alternating ' \ - "names and values pairs, #{values.inspect} given" - ) + "names and values pairs, #{values.inspect} given") @values = values.each_slice(2).map do |(name, value)| [String(name), value] end From b401e7af88723b5c1883f3b168ccfe3fc0baa92b Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Fri, 21 Oct 2016 15:08:56 -0700 Subject: [PATCH 158/196] Support C* 4.x. --- lib/cassandra/driver.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/cassandra/driver.rb b/lib/cassandra/driver.rb index 42ba0881d..8b48f34b9 100644 --- a/lib/cassandra/driver.rb +++ b/lib/cassandra/driver.rb @@ -250,6 +250,9 @@ def create_schema_fetcher_picker picker.when('3.') do Cluster::Schema::Fetchers::V3_0_x.new(schema_cql_type_parser, cluster_schema) end + picker.when('4.') do + Cluster::Schema::Fetchers::V3_0_x.new(schema_cql_type_parser, cluster_schema) + end picker end From 234fc37489317a27a91a766bfcbcbc4b382c9938 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Fri, 21 Oct 2016 15:50:35 -0700 Subject: [PATCH 159/196] RUBY-267 - Support iterating execution profiles. --- lib/cassandra/cluster.rb | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/lib/cassandra/cluster.rb b/lib/cassandra/cluster.rb index fe86d3ddd..c09eca0bc 100644 --- a/lib/cassandra/cluster.rb +++ b/lib/cassandra/cluster.rb @@ -167,11 +167,23 @@ def execution_profile(name) @profile_manager.profiles[name] end - # @return [Hash] the collection of execution profiles - def execution_profiles - # Return a dup of the hash to prevent the user from adding/removing profiles from the profile-manager. - @profile_manager.profiles.dup + # Yield or enumerate each execution profile defined in this cluster + # @overload each_execution_profile + # @yieldparam name [String, Symbol] name of current profile + # @yieldparam profile [Cassandra::Execution::Profile] current profile + # @return [Cassandra::Cluster] self + # @overload each_execution_profile + # @return [Hash] a hash of profiles keyed on name + def each_execution_profile(&block) + if block_given? + @profile_manager.profiles.each_pair(&block) + self + else + # Return a dup of the hash to prevent the user from adding/removing profiles from the profile-manager. + @profile_manager.profiles.dup + end end + alias execution_profiles each_execution_profile # @!method refresh_schema_async # Trigger an asynchronous schema metadata refresh From aec9f424d34d5d0aaed47f6261ffd2877f104dd5 Mon Sep 17 00:00:00 2001 From: Kishan Karunaratne Date: Fri, 21 Oct 2016 15:53:32 -0700 Subject: [PATCH 160/196] [RUBY-235] Integration tests for execution info retries --- integration/idempotency_test.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/integration/idempotency_test.rb b/integration/idempotency_test.rb index 7f1ab16c2..839509c5d 100644 --- a/integration/idempotency_test.rb +++ b/integration/idempotency_test.rb @@ -62,6 +62,7 @@ def test_statement_idempotency_on_timeout end info = session.execute('SELECT * FROM test', consistency: :one, idempotent: true).execution_info + assert_equal 1, info.retries assert_equal 2, info.hosts.size assert_equal '127.0.0.1', info.hosts[0].ip.to_s assert_equal '127.0.0.2', info.hosts[1].ip.to_s @@ -101,6 +102,7 @@ def test_statement_idempotency_on_timeout_no_keyspace_predefined end info = session.execute('SELECT * FROM simplex.test', consistency: :one, idempotent: true).execution_info + assert_equal 1, info.retries assert_equal 2, info.hosts.size assert_equal '127.0.0.1', info.hosts[0].ip.to_s assert_equal '127.0.0.2', info.hosts[1].ip.to_s From 02b5b55c8e8d0d900f96b6bf0a1cac7ab7e19ef4 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 24 Oct 2016 11:53:34 -0700 Subject: [PATCH 161/196] Added documentation around prepared statement id-caching. --- features/basics/prepared_statements.feature | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/features/basics/prepared_statements.feature b/features/basics/prepared_statements.feature index 0c0c0263c..7dd4145a1 100644 --- a/features/basics/prepared_statements.feature +++ b/features/basics/prepared_statements.feature @@ -4,7 +4,12 @@ Feature: Prepared statements it multiple times with different values. A bind variable marker `?` is used to represent a dynamic value in a statement. - Scenario: an INSERT statement is prepared + The driver caches prepared statement id's in such a way that + when a query is to be prepared for a node, the "preparation" is bypassed if the statement has previously + been prepared on any other node. This reduces a round-trip to the node while also optimizing query execution + performance. + + Scenario: An INSERT statement is prepared Given a running cassandra cluster with schema: """cql CREATE KEYSPACE simplex WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 3}; @@ -72,7 +77,7 @@ Feature: Prepared statements """ @cassandra-version-specific @cassandra-version-2.1 - Scenario: an INSERT statement is prepared with named parameters + Scenario: An INSERT statement is prepared with named parameters Given a running cassandra cluster with schema: """cql CREATE KEYSPACE simplex WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 3}; @@ -140,7 +145,7 @@ Feature: Prepared statements """ @cassandra-version-specific @cassandra-version-2.0 - Scenario: a SELECT statement with parameterized LIMIT is prepared + Scenario: A SELECT statement with parameterized LIMIT is prepared Given a running cassandra cluster with schema: """cql CREATE KEYSPACE simplex WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 3}; @@ -284,4 +289,4 @@ Feature: Prepared statements Then its output should contain: """ Joséphine Baker: La Petite Tonkinoise / Bye Bye Blackbird - """ \ No newline at end of file + """ From 8bc43b8899ba25f7da242b29fa6d2f6cbadf6052 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 24 Oct 2016 16:29:18 -0700 Subject: [PATCH 162/196] Add debug output around sporadically failing idempotency integration tests. --- integration/idempotency_test.rb | 37 +++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/integration/idempotency_test.rb b/integration/idempotency_test.rb index 839509c5d..13aabd18c 100644 --- a/integration/idempotency_test.rb +++ b/integration/idempotency_test.rb @@ -61,11 +61,19 @@ def test_statement_idempotency_on_timeout session.execute('SELECT * FROM test', consistency: :one) end - info = session.execute('SELECT * FROM test', consistency: :one, idempotent: true).execution_info - assert_equal 1, info.retries - assert_equal 2, info.hosts.size - assert_equal '127.0.0.1', info.hosts[0].ip.to_s - assert_equal '127.0.0.2', info.hosts[1].ip.to_s + info = nil + begin + info = session.execute('SELECT * FROM test', consistency: :one, idempotent: true).execution_info + assert_equal 1, info.retries + assert_equal 2, info.hosts.size + assert_equal '127.0.0.1', info.hosts[0].ip.to_s + assert_equal '127.0.0.2', info.hosts[1].ip.to_s + rescue Cassandra::Errors::ReadTimeoutError => e + # Every once in a while, the test fails with a ReadTimeoutError. Try to report extra info in that case that + # may help us track down the core issue. + info = e.execution_info + assert_equal('', info.inspect, 'Got ReadTimeoutError when statement should have succeeded') + end ensure @@ccm_cluster.unblock_nodes cluster && cluster.close @@ -101,14 +109,21 @@ def test_statement_idempotency_on_timeout_no_keyspace_predefined session.execute('SELECT * FROM simplex.test', consistency: :one) end - info = session.execute('SELECT * FROM simplex.test', consistency: :one, idempotent: true).execution_info - assert_equal 1, info.retries - assert_equal 2, info.hosts.size - assert_equal '127.0.0.1', info.hosts[0].ip.to_s - assert_equal '127.0.0.2', info.hosts[1].ip.to_s + info = nil + begin + info = session.execute('SELECT * FROM simplex.test', consistency: :one, idempotent: true).execution_info + assert_equal 1, info.retries + assert_equal 2, info.hosts.size + assert_equal '127.0.0.1', info.hosts[0].ip.to_s + assert_equal '127.0.0.2', info.hosts[1].ip.to_s + rescue Cassandra::Errors::ReadTimeoutError => e + # Every once in a while, the test fails with a ReadTimeoutError. Try to report extra info in that case that + # may help us track down the core issue. + info = e.execution_info + assert_equal('', info.inspect, 'Got ReadTimeoutError when statement should have succeeded') + end ensure @@ccm_cluster.unblock_nodes cluster && cluster.close end - end From abe076e01c194e4b16c56d4d2f4edab83bc0365c Mon Sep 17 00:00:00 2001 From: Kishan Karunaratne Date: Tue, 25 Oct 2016 13:19:59 -0700 Subject: [PATCH 163/196] [RUBY-257] Integration tests for prepared statement repreparing --- integration/control_connection_test.rb | 39 ++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/integration/control_connection_test.rb b/integration/control_connection_test.rb index 9287e829e..b41fc7f6a 100644 --- a/integration/control_connection_test.rb +++ b/integration/control_connection_test.rb @@ -88,4 +88,43 @@ def test_missing_peer_columns end end end + + # Test for repreparing statements on another host + # + # test_can_reprepare_statements_automatically tests that prepared statements are automatically reprepared on a host + # if that host does not already have the prepared statement in its cache. It first creates a simple keyspace + # and table to be used. It then prepares an insert statement on node2, by keeping node1 down. It then brings node1 + # back up but brings node2 down. Finally it executes the prepared statement on node1, and verifies that the query + # is executed successfully using node1. + # + # @since 3.1.0 + # @jira_ticket RUBY-257 + # @expected_result Node1 should be able to be used to execute the prepared statement + # + # @test_assumptions A 2-node Cassandra cluster. + # @test_category prepared_statements:preparation + # + def test_can_reprepare_statements_automatically + cluster = Cassandra.cluster + session = cluster.connect + + session.execute("CREATE KEYSPACE simplex WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 2}") + session.execute("USE simplex") + session.execute("CREATE TABLE test (k int, v int, PRIMARY KEY (k, v))") + + # Prepare on node2 + @@ccm_cluster.stop_node('node1') + insert = session.prepare("INSERT INTO test (k,v) VALUES (?, ?)") + assert_equal 1, insert.execution_info.hosts.size + assert_equal '127.0.0.2', insert.execution_info.hosts.first.ip.to_s + @@ccm_cluster.start_node('node1') + + # Insert using node1 + @@ccm_cluster.stop_node('node2') + info = session.execute(insert, arguments: [0,0]).execution_info + assert_equal 1, info.hosts.size + assert_equal '127.0.0.1', info.hosts.first.ip.to_s + ensure + cluster.close + end end From fad98f98478d3f719485639e7be6fab90f7ea7da Mon Sep 17 00:00:00 2001 From: Kishan Karunaratne Date: Tue, 25 Oct 2016 16:42:27 -0700 Subject: [PATCH 164/196] [RUBY-256] Integration tests and cucumber features for execution profiles --- features/basics/execution_profiles.feature | 51 ++++++++++++++++--- integration/session_test.rb | 59 ++++++++++++++++++++++ 2 files changed, 104 insertions(+), 6 deletions(-) diff --git a/features/basics/execution_profiles.feature b/features/basics/execution_profiles.feature index 161377e30..d4c557efe 100644 --- a/features/basics/execution_profiles.feature +++ b/features/basics/execution_profiles.feature @@ -6,7 +6,49 @@ Feature: Execution profiles Background: Given a running cassandra cluster - Scenario: Configure different load balancing policies with profiles. + Scenario: Creating and inspecting execution profiles + Given the following example: + """ruby + require 'cassandra' + + profile_1 = Cassandra::Execution::Profile.new(load_balancing_policy: Cassandra::LoadBalancing::Policies::RoundRobin.new, + retry_policy: Cassandra::Retry::Policies::DowngradingConsistency.new, + consistency: :all, + timeout: 32 + ) + + cluster = Cassandra.cluster(execution_profiles: {'my_profile' => profile_1}) + puts "There are #{cluster.execution_profiles.size} execution profiles in the cluster:" + puts "" + + cluster.execution_profiles.each do |name, profile| + puts "Name: #{name}" + puts "Load_balancing_policy: #{profile.load_balancing_policy.class}" + puts "Retry policy: #{profile.retry_policy.class}" + puts "Consistency: #{profile.consistency}" + puts "Timeout: #{profile.timeout}" + puts "" + end + """ + When it is executed + Then its output should contain: + """ + There are 2 execution profiles in the cluster: + + Name: __DEFAULT_EXECUTION_PROFILE__ + Load_balancing_policy: Cassandra::LoadBalancing::Policies::TokenAware + Retry policy: Cassandra::Retry::Policies::Default + Consistency: local_one + Timeout: 12 + + Name: my_profile + Load_balancing_policy: Cassandra::LoadBalancing::Policies::RoundRobin + Retry policy: Cassandra::Retry::Policies::DowngradingConsistency + Consistency: all + Timeout: 32 + """ + + Scenario: Configure different load balancing policies with profiles Given the following example: """ruby require 'cassandra' @@ -22,9 +64,7 @@ Feature: Execution profiles puts "Running with default profile" - # By default, the driver uses a dc-aware, token-aware round-robin load balancing policy that - # is notified of which nodes are available in random order. To make this test's output - # deterministic, we sort the results by ip address. + # By default, the driver uses a token-aware, round-robin load balancing policy. ip_list = [] 3.times do rs = session.execute('select rpc_address from system.local') @@ -32,8 +72,7 @@ Feature: Execution profiles end puts ip_list.sort.join("\n") - # p2 and p3 set up load-balancing policies that will match only one node, so there's no - # issue of hitting nodes in random order. + # p2 and p3 set up load-balancing policies that will match only one node. puts "Running with profile p1" 3.times do rs = session.execute('select rpc_address from system.local', execution_profile: :p1) diff --git a/integration/session_test.rb b/integration/session_test.rb index 55b507fe7..01c8fa6a2 100644 --- a/integration/session_test.rb +++ b/integration/session_test.rb @@ -870,6 +870,8 @@ def test_cluster_session_inspect assert_match(/@paging_state=nil/, session_inspect) assert_match(/@idempotent=false/, session_inspect) assert_match(/@payload=nil/, session_inspect) + ensure + cluster && cluster.close end # Test for cluster options retrieval @@ -900,5 +902,62 @@ def test_can_retrieve_cluster_options assert_equal Cassandra::Retry::Policies::Default, execution_profile.retry_policy.class assert_equal :quorum, execution_profile.consistency assert_equal 12, execution_profile.timeout + ensure + cluster && cluster.close + end + + # Test for execution profile creation and use + # + # test_can_use_execution_profiles tests that execution profiles can be created and used in session execution. It first + # creates two execution profiles: one with all options specified and another with no options specified. It then + # creates a cluster with these two profiles and verifies their options. The 2nd execution profile should be equivalent + # to the DEFAULT_EXECUTION_PROFILE as any missing options are copied over. It then executes a simple query using + # the execution profiles and verifies that the execution info shows their use. + # + # @since 3.1.0 + # @jira_ticket RUBY-256 + # @expected_result cluster execution profiles should be retrieved and used from the cluster object + # + # @test_category execution_profiles + # + def test_can_use_execution_profiles + setup_schema + + profile_1 = Cassandra::Execution::Profile.new(load_balancing_policy: Cassandra::LoadBalancing::Policies::RoundRobin.new, + retry_policy: Cassandra::Retry::Policies::DowngradingConsistency.new, + consistency: :all, + timeout: 32 + ) + + profile_2 = Cassandra::Execution::Profile.new(load_balancing_policy: nil, + retry_policy: nil, + consistency: nil, + ) + + profiles = {profile_1: profile_1, profile_2: profile_2} + cluster = Cassandra.cluster(execution_profiles: profiles) + assert_equal 3, cluster.execution_profiles.size + + execution_profile_1 = cluster.execution_profiles[:profile_1] + assert_equal profile_1, execution_profile_1 + + execution_profile_2 = cluster.execution_profiles[:profile_2] + assert_equal profile_2, execution_profile_2 + assert_equal cluster.execution_profiles['__DEFAULT_EXECUTION_PROFILE__'], execution_profile_2 + + session = cluster.connect + exec_options = session.execute('select * from system.local').execution_info.options + assert_equal Cassandra::LoadBalancing::Policies::TokenAware, exec_options.load_balancing_policy.class + assert_equal Cassandra::Retry::Policies::Default, exec_options.retry_policy.class + assert_equal :local_one, exec_options.consistency + assert_equal 12, exec_options.timeout + + exec_options = session.execute('select * from system.local', execution_profile: :profile_1).execution_info.options + assert_equal Cassandra::LoadBalancing::Policies::RoundRobin, exec_options.load_balancing_policy.class + assert_equal Cassandra::Retry::Policies::DowngradingConsistency, exec_options.retry_policy.class + assert_equal :all, exec_options.consistency + assert_equal 32, exec_options.timeout + ensure + cluster && cluster.close end end From 5fdf81eb4ab11165e92b215895bff85e928b3ed6 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Wed, 26 Oct 2016 14:39:23 -0700 Subject: [PATCH 165/196] Improved error messages when request connections are created/closed. --- lib/cassandra/cluster/client.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/cassandra/cluster/client.rb b/lib/cassandra/cluster/client.rb index e96e56aba..f693eed2b 100644 --- a/lib/cassandra/cluster/client.rb +++ b/lib/cassandra/cluster/client.rb @@ -473,7 +473,7 @@ def connect_to_host(host, pool_size) @pending_connections[host] += size end - @logger.debug("Creating #{size} connections to #{host.ip}") + @logger.debug("Creating #{size} request connections to #{host.ip}") futures = size.times.map do @connector.connect(host).recover do |e| FailedConnection.new(e, host) @@ -492,7 +492,7 @@ def connect_to_host(host, pool_size) end end - @logger.debug("Created #{connections.size} connections to #{host.ip}") + @logger.debug("Created #{connections.size} request connections to #{host.ip}") pool = nil @@ -511,6 +511,12 @@ def connect_to_host(host, pool_size) connections.each do |connection| connection.on_closed do |cause| + if cause + @logger.info('Request connection closed ' \ + "(#{cause.class.name}: #{cause.message})") + else + @logger.info('Request connection closed') + end connect_to_host_maybe_retry(host, pool_size) if cause end end From 5d392ae03ef142546bd50d92bfee6ed34d7b10ea Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 31 Oct 2016 11:54:39 -0700 Subject: [PATCH 166/196] RUBY-272 - Different types of execution profiles should inherit defaults from different parent profiles. --- lib/cassandra.rb | 2 +- lib/cassandra/execution/profile.rb | 12 +++++++ lib/cassandra/execution/profile_manager.rb | 34 ++++++++++++++++++- .../execution/profile_manager_spec.rb | 10 +++++- 4 files changed, 55 insertions(+), 3 deletions(-) diff --git a/lib/cassandra.rb b/lib/cassandra.rb index c85aa6dae..87d761062 100644 --- a/lib/cassandra.rb +++ b/lib/cassandra.rb @@ -877,8 +877,8 @@ def self.validate_and_massage_options(options) require 'cassandra/execution/info' require 'cassandra/execution/options' -require 'cassandra/execution/profile' require 'cassandra/execution/profile_manager' +require 'cassandra/execution/profile' require 'cassandra/execution/trace' require 'cassandra/load_balancing' diff --git a/lib/cassandra/execution/profile.rb b/lib/cassandra/execution/profile.rb index 00d6dcba3..a0e69bbbc 100644 --- a/lib/cassandra/execution/profile.rb +++ b/lib/cassandra/execution/profile.rb @@ -42,6 +42,9 @@ class Profile # @return [Numeric] request execution timeout in seconds. `nil` means there is no timeout. attr_reader :timeout + # @private + attr_accessor :parent_name + # @private DEFAULT_OPTIONS = {load_balancing_policy: nil, retry_policy: nil, @@ -51,6 +54,9 @@ class Profile # @private DEFAULT_TIMEOUT = 12 + # @private + DEFAULT_PARENT_NAME = Cassandra::Execution::ProfileManager::DEFAULT_EXECUTION_PROFILE + # @param options [Hash] hash of attributes. Unspecified attributes or attributes with nil values effectively # fall back to the attributes in the default execution profile. # @option options [Numeric] :timeout (12) Request execution timeout in @@ -67,6 +73,7 @@ def initialize(options = {}) @retry_policy = options[:retry_policy] @consistency = options[:consistency] @timeout = options[:timeout] + @parent_name = DEFAULT_PARENT_NAME end def timeout @@ -122,6 +129,11 @@ def merge_from(parent_profile) @timeout = parent_profile.timeout if @timeout == :unspecified self end + + # @private + def well_formed? + !@load_balancing_policy.nil? && !@retry_policy.nil? && !@consistency.nil? && @timeout != :unspecified + end end end end diff --git a/lib/cassandra/execution/profile_manager.rb b/lib/cassandra/execution/profile_manager.rb index ab17803eb..2af86817a 100644 --- a/lib/cassandra/execution/profile_manager.rb +++ b/lib/cassandra/execution/profile_manager.rb @@ -35,6 +35,8 @@ def initialize(default_profile, profiles) @load_balancing_policies << default_profile.load_balancing_policy if default_profile.load_balancing_policy @profiles = {DEFAULT_EXECUTION_PROFILE => default_profile} + @unready_profiles = {} + profiles.each do |name, profile| add_profile(name, profile) end @@ -60,9 +62,39 @@ def distance(host) # NOTE: It's only safe to call add_profile when setting up the cluster object. In particular, # this is only ok before calling Driver#connect. def add_profile(name, profile) + if !profile.well_formed? && @profiles.key?(profile.parent_name) + # This profile is ready to inherit attributes from its parent. + profile.merge_from(@profiles[profile.parent_name]) + end + if profile.well_formed? + make_available(name, profile) + did_add = true + while did_add && !@unready_profiles.empty? + did_add = false + @unready_profiles.dup.each do |name, profile| + did_add = hydrate_profile(name, profile) + end + end + else + # This profile isn't ready to inherit its parent attributes yet + @unready_profiles[name] = profile + end + end + + def make_available(name, profile) @profiles[name] = profile @load_balancing_policies << profile.load_balancing_policy if profile.load_balancing_policy - profile.merge_from(default_profile) + @unready_profiles.delete(name) + end + + def hydrate_profile(name, profile) + did_work = false + if @profiles.key?(profile.parent_name) + profile.merge_from(@profiles[profile.parent_name]) + make_available(name, profile) + did_work = true + end + did_work end # @private diff --git a/spec/cassandra/execution/profile_manager_spec.rb b/spec/cassandra/execution/profile_manager_spec.rb index c3d7f379a..18fd3a431 100644 --- a/spec/cassandra/execution/profile_manager_spec.rb +++ b/spec/cassandra/execution/profile_manager_spec.rb @@ -33,11 +33,18 @@ module Execution let(:profile3) { Profile.new(load_balancing_policy: lbp3) } let(:profile4) { Profile.new } let(:profile5) { Profile.new(load_balancing_policy: lbp1) } + let(:profile6) { + # This profile's parent is *not* the default, but rather one of the other profiles. + p = Profile.new + p.parent_name = :p3 + p + } + let(:default_profile) { Profile.new(load_balancing_policy: lbp1, retry_policy: retry_policy, consistency: :quorum) } let(:subject) { - ProfileManager.new(default_profile, {p1: profile1, p2: profile2, p3: profile3, p4: profile4, p5: profile5}) + ProfileManager.new(default_profile, {p6: profile6, p1: profile1, p2: profile2, p3: profile3, p4: profile4, p5: profile5}) } it 'should fill out profiles with values from default profile' do @@ -52,6 +59,7 @@ module Execution consistency: :quorum, timeout: Profile::DEFAULT_TIMEOUT)) expect(profile5).to eq(Profile.new(load_balancing_policy: lbp1, retry_policy: retry_policy, consistency: :quorum, timeout: Profile::DEFAULT_TIMEOUT)) + expect(profile6).to eq(profile3) end it 'should return unique list of lbps' do From 5512444b3111d0e784eaa771a9687fd3ef08bd45 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 31 Oct 2016 18:07:34 -0700 Subject: [PATCH 167/196] Refined documentation on execution profiles. --- features/basics/execution_profiles.feature | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/features/basics/execution_profiles.feature b/features/basics/execution_profiles.feature index d4c557efe..aa4ca725e 100644 --- a/features/basics/execution_profiles.feature +++ b/features/basics/execution_profiles.feature @@ -3,6 +3,16 @@ Feature: Execution profiles Execution profiles allow a user to group various execution options into a 'profile'. A user can then execute statements with different profiles by specifying the profile name. + Profile names should be strings or symbols. In this release, a profile encapsulates load-balancing policy, + retry-policy, consistency-level, and timeout. + + When a user specifies simple options to `Cassandra.cluster`, the options mentioned above get stored in a default + execution profile. This execution profile is used by default by `Session.execute*` methods. User-defined execution + profiles fall back to values from the default profile for unspecified attributes. This allows the user to override + only the options she wants to set differently from the default profile. + + Finally, options specified to `Session.execute*` methods override options specified in the desired execution profile. + Background: Given a running cassandra cluster From d10c4edc7497191b91da551cc48f8a9484aa932e Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Wed, 2 Nov 2016 09:34:05 -0700 Subject: [PATCH 168/196] RUBY-276 - execution profile attributes should initially be :unspecified * Moved execution profile validation logic out of Cassandra.validate_and_massage_options and into execution profile constructor. This will keep bad profiles from being created in the first place and report errors to the user sooner. --- lib/cassandra.rb | 79 +------------------ lib/cassandra/driver.rb | 2 +- lib/cassandra/execution/profile.rb | 74 ++++++++++++----- spec/cassandra/execution/options_spec.rb | 24 +++--- .../execution/profile_manager_spec.rb | 29 +++++-- spec/cassandra/execution/profile_spec.rb | 67 ++++++++++++---- spec/cassandra/session_spec.rb | 7 +- spec/cassandra_spec.rb | 59 -------------- 8 files changed, 150 insertions(+), 191 deletions(-) diff --git a/lib/cassandra.rb b/lib/cassandra.rb index 87d761062..f272cb377 100644 --- a/lib/cassandra.rb +++ b/lib/cassandra.rb @@ -517,25 +517,6 @@ def self.validate_and_massage_options(options) ':execution_profiles must be a hash of entries.') end - if options.key?(:timeout) - timeout = options[:timeout] - - unless timeout.nil? - Util.assert_instance_of(::Numeric, timeout, ":timeout must be a number of seconds, #{timeout.inspect} given") - Util.assert(timeout > 0, ":timeout must be greater than 0, #{timeout} given") - end - end - if options.key?(:execution_profiles) - options[:execution_profiles].each do |name, profile| - timeout = profile.timeout - next if timeout.nil? - Util.assert_instance_of(::Numeric, timeout, - ":timeout of execution profile #{name} must be a number of seconds, " \ - "#{timeout.inspect} given") - Util.assert(timeout > 0, ":timeout of execution profile #{name} must be greater than 0, #{timeout} given") - end - end - if options.key?(:heartbeat_interval) timeout = options[:heartbeat_interval] @@ -584,28 +565,6 @@ def self.validate_and_massage_options(options) end end - if options.key?(:load_balancing_policy) - load_balancing_policy = options[:load_balancing_policy] - methods = [:host_up, :host_down, :host_found, :host_lost, :setup, :teardown, - :distance, :plan] - Util.assert_responds_to_all(methods, load_balancing_policy) do - ":load_balancing_policy #{load_balancing_policy.inspect} must respond " \ - "to #{methods.inspect}, but doesn't" - end - end - if options.key?(:execution_profiles) - methods = [:host_up, :host_down, :host_found, :host_lost, :setup, :teardown, - :distance, :plan] - options[:execution_profiles].each do |name, profile| - load_balancing_policy = profile.load_balancing_policy - next if load_balancing_policy.nil? - Util.assert_responds_to_all(methods, load_balancing_policy, - ":load_balancing_policy in execution profile #{name} " \ - "#{load_balancing_policy.inspect} must respond " \ - "to #{methods.inspect}, but doesn't") - end - end - if options.key?(:reconnection_policy) reconnection_policy = options[:reconnection_policy] @@ -615,43 +574,11 @@ def self.validate_and_massage_options(options) end end - if options.key?(:retry_policy) - retry_policy = options[:retry_policy] - methods = [:read_timeout, :write_timeout, :unavailable] - Util.assert_responds_to_all(methods, retry_policy) do - ":retry_policy #{retry_policy.inspect} must respond to #{methods.inspect}, " \ - "but doesn't" - end - end - if options.key?(:execution_profiles) - methods = [:read_timeout, :write_timeout, :unavailable] - options[:execution_profiles].each do |name, profile| - retry_policy = profile.retry_policy - next if retry_policy.nil? - Util.assert_responds_to_all(methods, retry_policy, - ":retry_policy in execution profile #{name} #{retry_policy.inspect} must " \ - "respond to #{methods.inspect}, but doesn't") - end - end + # Validate options that go in an execution profile. Instantiating one + # causes validation automatically. + Cassandra::Execution::Profile.new(options) options[:listeners] = Array(options[:listeners]) if options.key?(:listeners) - - if options.key?(:consistency) - consistency = options[:consistency] - Util.assert_one_of(CONSISTENCIES, consistency, - ":consistency must be one of #{CONSISTENCIES.inspect}, " \ - "#{consistency.inspect} given") - end - if options.key?(:execution_profiles) - options[:execution_profiles].each do |name, profile| - consistency = profile.consistency - next if consistency.nil? - Util.assert_one_of(CONSISTENCIES, consistency, - ":consistency in execution profile #{name} must be one of #{CONSISTENCIES.inspect}, " \ - "#{consistency.inspect} given") - end - end - options[:nodelay] = !!options[:nodelay] if options.key?(:nodelay) options[:trace] = !!options[:trace] if options.key?(:trace) options[:shuffle_replicas] = !!options[:shuffle_replicas] if options.key?(:shuffle_replicas) diff --git a/lib/cassandra/driver.rb b/lib/cassandra/driver.rb index 8b48f34b9..f08d7a806 100644 --- a/lib/cassandra/driver.rb +++ b/lib/cassandra/driver.rb @@ -173,7 +173,7 @@ def self.let(name, &block) let(:page_size) { 10000 } let(:heartbeat_interval) { 30 } let(:idle_timeout) { 60 } - let(:timeout) { Cassandra::Execution::Profile::DEFAULT_TIMEOUT } + let(:timeout) { 12 } let(:synchronize_schema) { true } let(:schema_refresh_delay) { 1 } let(:schema_refresh_timeout) { 10 } diff --git a/lib/cassandra/execution/profile.rb b/lib/cassandra/execution/profile.rb index a0e69bbbc..0d4901e74 100644 --- a/lib/cassandra/execution/profile.rb +++ b/lib/cassandra/execution/profile.rb @@ -46,28 +46,26 @@ class Profile attr_accessor :parent_name # @private - DEFAULT_OPTIONS = {load_balancing_policy: nil, - retry_policy: nil, - consistency: nil, + DEFAULT_OPTIONS = {load_balancing_policy: :unspecified, + retry_policy: :unspecified, + consistency: :unspecified, timeout: :unspecified}.freeze - # @private - DEFAULT_TIMEOUT = 12 - # @private DEFAULT_PARENT_NAME = Cassandra::Execution::ProfileManager::DEFAULT_EXECUTION_PROFILE # @param options [Hash] hash of attributes. Unspecified attributes or attributes with nil values effectively # fall back to the attributes in the default execution profile. - # @option options [Numeric] :timeout (12) Request execution timeout in + # @option options [Numeric] :timeout (:unspecified) Request execution timeout in # seconds. Setting value to `nil` will remove request timeout. - # @option options [Cassandra::LoadBalancing::Policy] :load_balancing_policy (nil) Load-balancing policy that - # determines which node will run the next statement. - # @option options [Cassandra::Retry::Policy] :retry_policy (nil) Retry policy that determines how request - # retries should be handled for different failure modes. - # @option options [Symbol] :consistency (nil) Consistency level with which to run statements. Must be one + # @option options [Cassandra::LoadBalancing::Policy] :load_balancing_policy (:unspecified) Load-balancing policy + # that determines which node will run the next statement. + # @option options [Cassandra::Retry::Policy] :retry_policy (:unspecified) Retry policy that determines how + # request retries should be handled for different failure modes. + # @option options [Symbol] :consistency (:unspecified) Consistency level with which to run statements. Must be one # of {Cassandra::CONSISTENCIES}. def initialize(options = {}) + validate(options) options = DEFAULT_OPTIONS.merge(options) @load_balancing_policy = options[:load_balancing_policy] @retry_policy = options[:retry_policy] @@ -76,17 +74,13 @@ def initialize(options = {}) @parent_name = DEFAULT_PARENT_NAME end - def timeout - @timeout == :unspecified ? DEFAULT_TIMEOUT : @timeout - end - # @private def to_h { load_balancing_policy: @load_balancing_policy, retry_policy: @retry_policy, consistency: @consistency, - timeout: timeout + timeout: @timeout } end @@ -96,7 +90,7 @@ def eql?(other) @load_balancing_policy == other.load_balancing_policy && \ @retry_policy == other.retry_policy && \ @consistency == other.consistency && \ - @timeout == other.instance_variable_get(:@timeout) + @timeout == other.timeout end alias == eql? @@ -121,11 +115,49 @@ def inspect "timeout=#{@timeout.inspect}>" end + # @private + def validate(options) + if options.key?(:timeout) + timeout = options[:timeout] + + unless timeout.nil? + Util.assert_instance_of(::Numeric, timeout, ":timeout must be a number of seconds, #{timeout.inspect} given") + Util.assert(timeout > 0, ":timeout must be greater than 0, #{timeout} given") + end + end + + if options.key?(:load_balancing_policy) + load_balancing_policy = options[:load_balancing_policy] + methods = [:host_up, :host_down, :host_found, :host_lost, :setup, :teardown, + :distance, :plan] + Util.assert_responds_to_all(methods, load_balancing_policy) do + ":load_balancing_policy #{load_balancing_policy.inspect} must respond " \ + "to #{methods.inspect}, but doesn't" + end + end + + if options.key?(:retry_policy) + retry_policy = options[:retry_policy] + methods = [:read_timeout, :write_timeout, :unavailable] + Util.assert_responds_to_all(methods, retry_policy) do + ":retry_policy #{retry_policy.inspect} must respond to #{methods.inspect}, " \ + "but doesn't" + end + end + + if options.key?(:consistency) + consistency = options[:consistency] + Util.assert_one_of(CONSISTENCIES, consistency, + ":consistency must be one of #{CONSISTENCIES.inspect}, " \ + "#{consistency.inspect} given") + end + end + # @private def merge_from(parent_profile) - @load_balancing_policy = parent_profile.load_balancing_policy if @load_balancing_policy.nil? - @retry_policy = parent_profile.retry_policy if @retry_policy.nil? - @consistency = parent_profile.consistency if @consistency.nil? + @load_balancing_policy = parent_profile.load_balancing_policy if @load_balancing_policy == :unspecified + @retry_policy = parent_profile.retry_policy if @retry_policy == :unspecified + @consistency = parent_profile.consistency if @consistency == :unspecified @timeout = parent_profile.timeout if @timeout == :unspecified self end diff --git a/spec/cassandra/execution/options_spec.rb b/spec/cassandra/execution/options_spec.rb index a8fe8a034..ea57672d1 100644 --- a/spec/cassandra/execution/options_spec.rb +++ b/spec/cassandra/execution/options_spec.rb @@ -22,20 +22,24 @@ module Cassandra module Execution describe(Options) do let(:load_balancing_policy) { double('lbp') } + let(:lbp2) { double('lbp2') } let(:retry_policy) { double('retry_policy') } let(:base_options) { Options.new(timeout: 10, consistency: :one, load_balancing_policy: load_balancing_policy, retry_policy: retry_policy) } + before do - allow(load_balancing_policy).to receive(:host_up) - allow(load_balancing_policy).to receive(:host_down) - allow(load_balancing_policy).to receive(:host_found) - allow(load_balancing_policy).to receive(:host_lost) - allow(load_balancing_policy).to receive(:setup) - allow(load_balancing_policy).to receive(:teardown) - allow(load_balancing_policy).to receive(:distance) - allow(load_balancing_policy).to receive(:plan) + [load_balancing_policy, lbp2].each do |policy| + allow(policy).to receive(:host_up) + allow(policy).to receive(:host_down) + allow(policy).to receive(:host_found) + allow(policy).to receive(:host_lost) + allow(policy).to receive(:setup) + allow(policy).to receive(:teardown) + allow(policy).to receive(:distance) + allow(policy).to receive(:plan) + end allow(retry_policy).to receive(:read_timeout) allow(retry_policy).to receive(:write_timeout) @@ -76,9 +80,9 @@ module Execution end it 'should override with execution-profile and simple attribute' do - profile = Profile.new(timeout: 21, consistency: :quorum) + profile = Profile.new(load_balancing_policy: lbp2, retry_policy: retry_policy, timeout: 21, consistency: :quorum) result = base_options.override(profile, timeout: 42) - expect(result.load_balancing_policy).to be(load_balancing_policy) + expect(result.load_balancing_policy).to be(lbp2) expect(result.retry_policy).to be(retry_policy) expect(result.consistency).to be(:quorum) expect(result.timeout).to eq(42) diff --git a/spec/cassandra/execution/profile_manager_spec.rb b/spec/cassandra/execution/profile_manager_spec.rb index 18fd3a431..032fc119e 100644 --- a/spec/cassandra/execution/profile_manager_spec.rb +++ b/spec/cassandra/execution/profile_manager_spec.rb @@ -41,24 +41,41 @@ module Execution } let(:default_profile) { - Profile.new(load_balancing_policy: lbp1, retry_policy: retry_policy, consistency: :quorum) + Profile.new(load_balancing_policy: lbp1, retry_policy: retry_policy, consistency: :quorum, timeout: 12) } let(:subject) { ProfileManager.new(default_profile, {p6: profile6, p1: profile1, p2: profile2, p3: profile3, p4: profile4, p5: profile5}) } + before do + [lbp1, lbp2, lbp3].each do |policy| + allow(policy).to receive(:host_up) + allow(policy).to receive(:host_down) + allow(policy).to receive(:host_found) + allow(policy).to receive(:host_lost) + allow(policy).to receive(:setup) + allow(policy).to receive(:teardown) + allow(policy).to receive(:distance) + allow(policy).to receive(:plan) + end + + allow(retry_policy).to receive(:read_timeout) + allow(retry_policy).to receive(:write_timeout) + allow(retry_policy).to receive(:unavailable) + end + it 'should fill out profiles with values from default profile' do subject expect(profile1).to eq(Profile.new(load_balancing_policy: lbp1, retry_policy: retry_policy, - consistency: :quorum, timeout: Profile::DEFAULT_TIMEOUT)) + consistency: :quorum, timeout: 12)) expect(profile2).to eq(Profile.new(load_balancing_policy: lbp2, retry_policy: retry_policy, - consistency: :quorum, timeout: Profile::DEFAULT_TIMEOUT)) + consistency: :quorum, timeout: 12)) expect(profile3).to eq(Profile.new(load_balancing_policy: lbp3, retry_policy: retry_policy, - consistency: :quorum, timeout: Profile::DEFAULT_TIMEOUT)) + consistency: :quorum, timeout: 12)) expect(profile4).to eq(Profile.new(load_balancing_policy: lbp1, retry_policy: retry_policy, - consistency: :quorum, timeout: Profile::DEFAULT_TIMEOUT)) + consistency: :quorum, timeout: 12)) expect(profile5).to eq(Profile.new(load_balancing_policy: lbp1, retry_policy: retry_policy, - consistency: :quorum, timeout: Profile::DEFAULT_TIMEOUT)) + consistency: :quorum, timeout: 12)) expect(profile6).to eq(profile3) end diff --git a/spec/cassandra/execution/profile_spec.rb b/spec/cassandra/execution/profile_spec.rb index 22c06f377..1ecc43ffd 100644 --- a/spec/cassandra/execution/profile_spec.rb +++ b/spec/cassandra/execution/profile_spec.rb @@ -20,37 +20,70 @@ module Cassandra module Execution - describe(Profile) do - let(:profile1) { Profile.new(load_balancing_policy: lbp1) } - let(:profile2) { Profile.new(load_balancing_policy: lbp2) } - let(:profile3) { Profile.new(load_balancing_policy: lbp3) } - let(:profile4) { Profile.new } + describe Profile do + let(:lbp) { double('lbp') } + let(:lbp2) { double('lbp2') } + let(:retry_policy) { double('retry_policy') } + let(:retry_policy2) { double('retry_policy2') } - context :timeout do - it 'should return default timeout value if unspecified' do - expect(Profile.new.timeout).to eq(12) + before do + [lbp, lbp2].each do |policy| + allow(policy).to receive(:host_up) + allow(policy).to receive(:host_down) + allow(policy).to receive(:host_found) + allow(policy).to receive(:host_lost) + allow(policy).to receive(:setup) + allow(policy).to receive(:teardown) + allow(policy).to receive(:distance) + allow(policy).to receive(:plan) end - it 'should return nil if set with nil' do - expect(Profile.new(timeout: nil).timeout).to be_nil + [retry_policy, retry_policy2].each do |policy| + allow(policy).to receive(:read_timeout) + allow(policy).to receive(:write_timeout) + allow(policy).to receive(:unavailable) + end + end + + context :initialize do + it 'should validate :load_balancing_policy option' do + expect(Profile.new(load_balancing_policy: lbp).load_balancing_policy).to eq(lbp) + ['junk', nil].each do |val| + expect { Profile.new(load_balancing_policy: val) }.to raise_error(ArgumentError) + end + end + + it 'should validate :retry_policy option' do + expect(Profile.new(retry_policy: retry_policy).retry_policy).to be(retry_policy) + expect { Profile.new(retry_policy: 'junk') }.to raise_error(ArgumentError) + expect { Profile.new(retry_policy: nil) }.to raise_error(ArgumentError) + end + + it 'should validate :timeout' do + ['a', -1, 0].each do |val| + expect { Profile.new(timeout: val) }.to raise_error(ArgumentError) + end + [0.5, 38, nil].each do |val| + expect(Profile.new(timeout: val).timeout).to eq(val) + end end - it 'should return timeout value if set with a real value' do - expect(Profile.new(timeout: 6).timeout).to eq(6) + it 'should validate :consistency' do + Cassandra::CONSISTENCIES.each do |c| + expect(Profile.new(consistency: c).consistency).to eq(c) + end + expect { Profile.new(consistency: 'foo') }.to raise_error(ArgumentError) end end context :merge_from do - let(:lbp) { double('lbp') } - let(:lbp2) { double('lbp2') } - let(:retry_policy) { double('retry_policy') } - let(:retry_policy2) { double('retry_policy2') } let(:default_profile) { Profile.new(load_balancing_policy: lbp, retry_policy: retry_policy, consistency: :one, timeout: 10)} let(:profile) { Profile.new(load_balancing_policy: lbp2, retry_policy: retry_policy2, consistency: :quorum, timeout: 23) } let(:empty_profile) { Profile.new } + it 'should accept all attributes from parent profile if it has no attributes itself' do expect(Profile.new.merge_from(default_profile)).to eq(default_profile) end @@ -61,7 +94,7 @@ module Execution it 'should respect nil timeout in parent' do parent_profile = Profile.new(timeout: nil) - expect(empty_profile.timeout).to eq(12) + expect(empty_profile.timeout).to eq(:unspecified) expect(empty_profile.merge_from(parent_profile).timeout).to be_nil end diff --git a/spec/cassandra/session_spec.rb b/spec/cassandra/session_spec.rb index cfa70fb72..502c64b25 100644 --- a/spec/cassandra/session_spec.rb +++ b/spec/cassandra/session_spec.rb @@ -32,7 +32,12 @@ module Cassandra } } let(:profile_manager) { double('profile_manager') } - let(:profile) { Execution::Profile.new } + let(:profile) { + Execution::Profile.new(load_balancing_policy: load_balancing_policy, + retry_policy: retry_policy, + consistency: :one, + timeout: 12) + } let(:session_options) { Execution::Options.new(default_options) } let(:client) { double('cassandra-driver') } let(:session) { diff --git a/spec/cassandra_spec.rb b/spec/cassandra_spec.rb index 76bb138ca..ed7b10234 100644 --- a/spec/cassandra_spec.rb +++ b/spec/cassandra_spec.rb @@ -223,20 +223,6 @@ def decompress end end - it 'should validate :timeout in a profile' do - ['a', -1, 0].each do |val| - profiles.merge!(bad_profile: Cassandra::Execution::Profile.new(timeout: val)) - expect { C.validate(execution_profiles: profiles) }.to raise_error(ArgumentError) - end - - profiles.delete(:bad_profile) - - [0.5, 38, nil].each do |val| - profiles.merge!(good_profile: Cassandra::Execution::Profile.new(timeout: val)) - expect(C.validate(execution_profiles: profiles)).to eq({ execution_profiles: profiles }) - end - end - it 'should validate :heartbeat_interval' do expect { C.validate(heartbeat_interval: 'a') }.to raise_error(ArgumentError) expect { C.validate(heartbeat_interval: -1) }.to raise_error(ArgumentError) @@ -280,18 +266,6 @@ def decompress end end - it 'should validate :load_balancing_policy option in a profile' do - profiles.merge!(good_profile: Cassandra::Execution::Profile.new(load_balancing_policy: lbp)) - expect(C.validate(execution_profiles: profiles)).to eq({ execution_profiles: profiles }) - - # nil is actually allowed, so we don't error out for it. - profiles.merge!(good_profile: Cassandra::Execution::Profile.new(load_balancing_policy: nil)) - expect(C.validate(execution_profiles: profiles)).to eq({ execution_profiles: profiles }) - - profiles.merge!(bad_profile: Cassandra::Execution::Profile.new(load_balancing_policy: 'junk')) - expect { C.validate(execution_profiles: profiles) }.to raise_error(ArgumentError) - end - it 'should validate :reconnection_policy option' do class GoodPolicy def schedule @@ -320,30 +294,6 @@ def unavailable expect { C.validate(retry_policy: nil) }.to raise_error(ArgumentError) end - it 'should validate :retry_policy option in a profile' do - class GoodPolicy - def read_timeout - end - - def write_timeout - end - - def unavailable - end - end - policy = GoodPolicy.new - - profiles.merge!(good_profile: Cassandra::Execution::Profile.new(retry_policy: policy)) - expect(C.validate(execution_profiles: profiles)).to eq({ execution_profiles: profiles }) - - # nil is actually allowed, so we don't error out for it. - profiles.merge!(good_profile: Cassandra::Execution::Profile.new(retry_policy: nil)) - expect(C.validate(execution_profiles: profiles)).to eq({ execution_profiles: profiles }) - - profiles.merge!(bad_profile: Cassandra::Execution::Profile.new(retry_policy: 'junk')) - expect { C.validate(execution_profiles: profiles) }.to raise_error(ArgumentError) - end - it 'should massage :listeners into an array' do expect(C.validate(listeners: 'a')).to eq({ listeners: ['a'] }) expect(C.validate(listeners: ['a'])).to eq({ listeners: ['a'] }) @@ -357,15 +307,6 @@ def unavailable expect { C.validate(consistency: 'foo') }.to raise_error(ArgumentError) end - it 'should validate :consistency in a profile' do - Cassandra::CONSISTENCIES.each do |c| - profiles.merge!(good_profile: Cassandra::Execution::Profile.new(consistency: c)) - expect(C.validate(execution_profiles: profiles)).to eq({ execution_profiles: profiles }) - end - profiles.merge!(bad_profile: Cassandra::Execution::Profile.new(consistency: 'foo')) - expect { C.validate(execution_profiles: profiles) }.to raise_error(ArgumentError) - end - it 'should massage :nodelay to a boolean' do expect(C.validate(nodelay: nil)).to eq({ nodelay: false }) expect(C.validate(nodelay: 1)).to eq({ nodelay: true }) From 3e36f698c190bb337b7536f89a3caf5d9e956fc2 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Wed, 2 Nov 2016 10:19:34 -0700 Subject: [PATCH 169/196] RUBY-276 - execution profile attributes should initially be :unspecified * Fixed broken integration test. --- integration/session_test.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/integration/session_test.rb b/integration/session_test.rb index 01c8fa6a2..12b11bd81 100644 --- a/integration/session_test.rb +++ b/integration/session_test.rb @@ -929,10 +929,7 @@ def test_can_use_execution_profiles timeout: 32 ) - profile_2 = Cassandra::Execution::Profile.new(load_balancing_policy: nil, - retry_policy: nil, - consistency: nil, - ) + profile_2 = Cassandra::Execution::Profile.new profiles = {profile_1: profile_1, profile_2: profile_2} cluster = Cassandra.cluster(execution_profiles: profiles) From f24d0310799219c138d9e224dd7661eaf9d50c63 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Wed, 2 Nov 2016 11:03:09 -0700 Subject: [PATCH 170/196] RUBY-276 - execution profile attributes should initially be :unspecified * Fixed bug in Profile.well_formed? that didn't handle :unspecified properly. --- lib/cassandra/execution/profile.rb | 5 ++++- spec/cassandra/execution/profile_spec.rb | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/cassandra/execution/profile.rb b/lib/cassandra/execution/profile.rb index 0d4901e74..da99ad7ee 100644 --- a/lib/cassandra/execution/profile.rb +++ b/lib/cassandra/execution/profile.rb @@ -164,7 +164,10 @@ def merge_from(parent_profile) # @private def well_formed? - !@load_balancing_policy.nil? && !@retry_policy.nil? && !@consistency.nil? && @timeout != :unspecified + !@load_balancing_policy.nil? && @load_balancing_policy != :unspecified && + !@retry_policy.nil? && @retry_policy != :unspecified && + !@consistency.nil? && @consistency != :unspecified && + @timeout != :unspecified end end end diff --git a/spec/cassandra/execution/profile_spec.rb b/spec/cassandra/execution/profile_spec.rb index 1ecc43ffd..2898565a9 100644 --- a/spec/cassandra/execution/profile_spec.rb +++ b/spec/cassandra/execution/profile_spec.rb @@ -104,6 +104,13 @@ module Execution expect(profile.merge_from(default_profile).timeout).to be_nil end end + + it 'should not be well-formed if it has unspecified attributes' do + expect(Profile.new(timeout: 7).well_formed?).to eq(false) + expect(Profile.new(load_balancing_policy: lbp2, timeout: 7).well_formed?).to eq(false) + expect(Profile.new(retry_policy: retry_policy, load_balancing_policy: lbp2, timeout: 7).well_formed?).to eq(false) + expect(Profile.new(consistency: :one, retry_policy: retry_policy, load_balancing_policy: lbp2, timeout: 7).well_formed?).to eq(true) + end end end end From dfb4eae076f691ea09f9f8a5719807de2358fa50 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Fri, 4 Nov 2016 10:10:13 -0700 Subject: [PATCH 171/196] RUBY-277: Execution Profile improvements in core driver * Disallow mixing of primitive options (that are managed by execution profiles) with execution profiles when initializing cluster. * Expose core default execution profile as the execution profile named :default (symbol). * When hydrating an execution profile provided by the user, do not mutate the profile that was passed in. Rather, create a new fully-defined execution profile that merges attributes of the default profile with those from the user-defined execution profile. This preserves the contract that execution profiles are immutable. * Support overriding the :default profile with a partially defined profile, which will obtain the rest of its attributes from system defaults. --- README.md | 14 ++++++- features/basics/execution_profiles.feature | 39 ++++++++++++++---- integration/session_test.rb | 6 +-- lib/cassandra.rb | 6 +++ lib/cassandra/execution/profile.rb | 15 ++++--- lib/cassandra/execution/profile_manager.rb | 30 ++++++++------ .../execution/profile_manager_spec.rb | 31 ++++++++------ spec/cassandra/execution/profile_spec.rb | 7 +++- spec/cassandra_spec.rb | 40 ++++++++++++++----- 9 files changed, 131 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index f90c3e219..064988ff6 100644 --- a/README.md +++ b/README.md @@ -101,9 +101,19 @@ In the examples directory, you can find [an example of how to wrap the ruby driv interface parity with cql-rb](https://github.com/datastax/ruby-driver/blob/master/examples/cql-rb-wrapper.rb) to assist you with gradual upgrade. -## What's new in v3.0 +## What's new in v3.1 + +This minor release introduces features and fixes around resiliency, schema metadata, usability, and performance. One +of the most user-impacting of these is the introduction of +[execution profiles](http://docs.datastax.com/en/developer/ruby-driver/3.1/features/basics/execution_profiles). +Execution profiles allow you to group various execution options into a 'profile' and you reference the desired +profile at execution time. Get the scoop +[here](http://docs.datastax.com/en/developer/ruby-driver/3.1/features/basics/execution_profiles). -See the [changelog](https://github.com/datastax/ruby-driver/blob/master/CHANGELOG.md) for details on patch versions. +See the [changelog](https://github.com/datastax/ruby-driver/blob/master/CHANGELOG.md) for more information on all +changes in this version and past versions. + +## What's new in v3.0 ### Features: diff --git a/features/basics/execution_profiles.feature b/features/basics/execution_profiles.feature index aa4ca725e..294999965 100644 --- a/features/basics/execution_profiles.feature +++ b/features/basics/execution_profiles.feature @@ -1,15 +1,38 @@ Feature: Execution profiles - Execution profiles allow a user to group various execution options into a 'profile'. A user can then execute - statements with different profiles by specifying the profile name. + Execution profiles allow a user to group various execution options into a 'profile'. A user defines profiles when + initializing the cluster object. A user can then execute statements with different profiles by specifying the profile + name in calls to `Session.execute*`. Profile names should be strings or symbols. In this release, a profile encapsulates load-balancing policy, - retry-policy, consistency-level, and timeout. + retry-policy, consistency-level, and timeout primitive options. - When a user specifies simple options to `Cassandra.cluster`, the options mentioned above get stored in a default - execution profile. This execution profile is used by default by `Session.execute*` methods. User-defined execution - profiles fall back to values from the default profile for unspecified attributes. This allows the user to override - only the options she wants to set differently from the default profile. + If a user specifies simple options to `Cassandra.cluster`, the options mentioned above get stored in a default + execution profile named `:default`. This execution profile is used by default by `Session.execute*` methods. + User-defined execution profiles fall back to values from the default profile for unspecified attributes. This allows + the user to override only the options she wants to set differently from the default profile. + + If you declare execution profiles, it is illegal to also include the primitive options mentioned above: + + ```ruby + puts "This is bad" + cluster = Cassandra.cluster(timeout: 7, execution_profiles: { + myprof: Cassandra::Execution::Profile.new(...) + }) + ``` + + To change default execution profile attributes and declare other execution profiles, you must declare the :default + profile when initializing the cluster: + + ```ruby + puts "This is legal" + cluster = Cassandra.cluster(execution_profiles: { + default: Cassandra::Execution::Profile.new(timeout: 7) + myprof: Cassandra::Execution::Profile.new(...) + }) + ``` + + Unspecified attributes fall back to the same system defaults that have been used in past versions of the driver. Finally, options specified to `Session.execute*` methods override options specified in the desired execution profile. @@ -45,7 +68,7 @@ Feature: Execution profiles """ There are 2 execution profiles in the cluster: - Name: __DEFAULT_EXECUTION_PROFILE__ + Name: default Load_balancing_policy: Cassandra::LoadBalancing::Policies::TokenAware Retry policy: Cassandra::Retry::Policies::Default Consistency: local_one diff --git a/integration/session_test.rb b/integration/session_test.rb index 12b11bd81..c2834d661 100644 --- a/integration/session_test.rb +++ b/integration/session_test.rb @@ -896,7 +896,7 @@ def test_can_retrieve_cluster_options assert_includes [1,2,3,4], cluster.protocol_version refute_nil cluster.keyspaces - execution_profile = cluster.execution_profiles['__DEFAULT_EXECUTION_PROFILE__'] + execution_profile = cluster.execution_profiles[:default] refute_nil execution_profile assert_equal Cassandra::LoadBalancing::Policies::TokenAware, execution_profile.load_balancing_policy.class assert_equal Cassandra::Retry::Policies::Default, execution_profile.retry_policy.class @@ -911,7 +911,7 @@ def test_can_retrieve_cluster_options # test_can_use_execution_profiles tests that execution profiles can be created and used in session execution. It first # creates two execution profiles: one with all options specified and another with no options specified. It then # creates a cluster with these two profiles and verifies their options. The 2nd execution profile should be equivalent - # to the DEFAULT_EXECUTION_PROFILE as any missing options are copied over. It then executes a simple query using + # to the :default execution profile as any missing options are copied over. It then executes a simple query using # the execution profiles and verifies that the execution info shows their use. # # @since 3.1.0 @@ -940,7 +940,7 @@ def test_can_use_execution_profiles execution_profile_2 = cluster.execution_profiles[:profile_2] assert_equal profile_2, execution_profile_2 - assert_equal cluster.execution_profiles['__DEFAULT_EXECUTION_PROFILE__'], execution_profile_2 + assert_equal cluster.execution_profiles[:default], execution_profile_2 session = cluster.connect exec_options = session.execute('select * from system.local').execution_info.options diff --git a/lib/cassandra.rb b/lib/cassandra.rb index f272cb377..b96cced7e 100644 --- a/lib/cassandra.rb +++ b/lib/cassandra.rb @@ -324,6 +324,12 @@ def self.validate_and_massage_options(options) CLUSTER_OPTIONS.include?(key) end + if options.key?(:execution_profiles) + [:load_balancing_policy, :retry_policy, :timeout, :consistency].each do |opt| + raise ::ArgumentError, "#{opt} is not allowed when execution profiles are used" if options.key?(opt) + end + end + has_username = options.key?(:username) has_password = options.key?(:password) if has_username || has_password diff --git a/lib/cassandra/execution/profile.rb b/lib/cassandra/execution/profile.rb index da99ad7ee..16a47eecd 100644 --- a/lib/cassandra/execution/profile.rb +++ b/lib/cassandra/execution/profile.rb @@ -52,7 +52,7 @@ class Profile timeout: :unspecified}.freeze # @private - DEFAULT_PARENT_NAME = Cassandra::Execution::ProfileManager::DEFAULT_EXECUTION_PROFILE + DEFAULT_PARENT_NAME = :default # @param options [Hash] hash of attributes. Unspecified attributes or attributes with nil values effectively # fall back to the attributes in the default execution profile. @@ -155,11 +155,14 @@ def validate(options) # @private def merge_from(parent_profile) - @load_balancing_policy = parent_profile.load_balancing_policy if @load_balancing_policy == :unspecified - @retry_policy = parent_profile.retry_policy if @retry_policy == :unspecified - @consistency = parent_profile.consistency if @consistency == :unspecified - @timeout = parent_profile.timeout if @timeout == :unspecified - self + return self if well_formed? + + parent_hash = parent_profile.to_h + self_hash = to_h + self_hash.each do |key, value| + self_hash[key] = parent_hash[key] if value == :unspecified + end + Profile.new(self_hash) end # @private diff --git a/lib/cassandra/execution/profile_manager.rb b/lib/cassandra/execution/profile_manager.rb index 2af86817a..adde89a2d 100644 --- a/lib/cassandra/execution/profile_manager.rb +++ b/lib/cassandra/execution/profile_manager.rb @@ -23,17 +23,22 @@ class ProfileManager attr_reader :profiles attr_reader :load_balancing_policies - # Name of the default execution profile. Use this constant as the key for an execution profile when initializing - # a {Cluster} to override the default execution profile with your own. - DEFAULT_EXECUTION_PROFILE = '__DEFAULT_EXECUTION_PROFILE__'.freeze - def initialize(default_profile, profiles) + # default_profile is the default profile that we constructed internally. However, the user can override it + # with their own :default profile, which may not be fully specified. See if we have such a profile and merge + # in the "system defaults" from the profile we generated. + + custom_default = profiles.delete(:default) + unless custom_default.nil? + default_profile = custom_default.merge_from(default_profile) + end + # Walk through the profiles and fill them out with attributes from the default profile when they're not # set. Also, save off all of the load-balancing policies for easy access. @load_balancing_policies = Set.new - @load_balancing_policies << default_profile.load_balancing_policy if default_profile.load_balancing_policy - @profiles = {DEFAULT_EXECUTION_PROFILE => default_profile} + @load_balancing_policies << default_profile.load_balancing_policy + @profiles = {default: default_profile} @unready_profiles = {} @@ -43,7 +48,7 @@ def initialize(default_profile, profiles) end def default_profile - @profiles[DEFAULT_EXECUTION_PROFILE] + @profiles[:default] end def distance(host) @@ -64,7 +69,7 @@ def distance(host) def add_profile(name, profile) if !profile.well_formed? && @profiles.key?(profile.parent_name) # This profile is ready to inherit attributes from its parent. - profile.merge_from(@profiles[profile.parent_name]) + profile = profile.merge_from(@profiles[profile.parent_name]) end if profile.well_formed? make_available(name, profile) @@ -83,18 +88,17 @@ def add_profile(name, profile) def make_available(name, profile) @profiles[name] = profile - @load_balancing_policies << profile.load_balancing_policy if profile.load_balancing_policy + @load_balancing_policies << profile.load_balancing_policy @unready_profiles.delete(name) end def hydrate_profile(name, profile) - did_work = false if @profiles.key?(profile.parent_name) - profile.merge_from(@profiles[profile.parent_name]) + profile = profile.merge_from(@profiles[profile.parent_name]) make_available(name, profile) - did_work = true + return true end - did_work + false end # @private diff --git a/spec/cassandra/execution/profile_manager_spec.rb b/spec/cassandra/execution/profile_manager_spec.rb index 032fc119e..067ff6fa8 100644 --- a/spec/cassandra/execution/profile_manager_spec.rb +++ b/spec/cassandra/execution/profile_manager_spec.rb @@ -23,10 +23,9 @@ module Execution describe(ProfileManager) do let(:cluster) { double('cluster') } let(:host) { double('host') } - let(:registry) { FakeClusterRegistry.new(['127.0.0.1', '127.0.0.2']) } - let(:lbp1) { FakeLoadBalancingPolicy.new(registry) } - let(:lbp2) { FakeLoadBalancingPolicy.new(registry) } - let(:lbp3) { FakeLoadBalancingPolicy.new(registry) } + let(:lbp1) { double('lbp1') } + let(:lbp2) { double('lbp2') } + let(:lbp3) { double('lbp3') } let(:retry_policy) { double('retry_policy') } let(:profile1) { Profile.new(load_balancing_policy: lbp1) } let(:profile2) { Profile.new(load_balancing_policy: lbp2) } @@ -39,13 +38,15 @@ module Execution p.parent_name = :p3 p } - let(:default_profile) { Profile.new(load_balancing_policy: lbp1, retry_policy: retry_policy, consistency: :quorum, timeout: 12) } let(:subject) { ProfileManager.new(default_profile, {p6: profile6, p1: profile1, p2: profile2, p3: profile3, p4: profile4, p5: profile5}) } + let(:subject_with_custom_default) { + ProfileManager.new(default_profile, {default: profile2, p4: profile4}) + } before do [lbp1, lbp2, lbp3].each do |policy| @@ -65,18 +66,24 @@ module Execution end it 'should fill out profiles with values from default profile' do - subject - expect(profile1).to eq(Profile.new(load_balancing_policy: lbp1, retry_policy: retry_policy, + expect(subject.profiles[:p1]).to eq(Profile.new(load_balancing_policy: lbp1, retry_policy: retry_policy, consistency: :quorum, timeout: 12)) - expect(profile2).to eq(Profile.new(load_balancing_policy: lbp2, retry_policy: retry_policy, + expect(subject.profiles[:p2]).to eq(Profile.new(load_balancing_policy: lbp2, retry_policy: retry_policy, consistency: :quorum, timeout: 12)) - expect(profile3).to eq(Profile.new(load_balancing_policy: lbp3, retry_policy: retry_policy, + expect(subject.profiles[:p3]).to eq(Profile.new(load_balancing_policy: lbp3, retry_policy: retry_policy, consistency: :quorum, timeout: 12)) - expect(profile4).to eq(Profile.new(load_balancing_policy: lbp1, retry_policy: retry_policy, + expect(subject.profiles[:p4]).to eq(Profile.new(load_balancing_policy: lbp1, retry_policy: retry_policy, consistency: :quorum, timeout: 12)) - expect(profile5).to eq(Profile.new(load_balancing_policy: lbp1, retry_policy: retry_policy, + expect(subject.profiles[:p5]).to eq(Profile.new(load_balancing_policy: lbp1, retry_policy: retry_policy, consistency: :quorum, timeout: 12)) - expect(profile6).to eq(profile3) + expect(subject.profiles[:p6]).to eq(subject.profiles[:p3]) + end + + it 'should respect custom default profile' do + expect(subject_with_custom_default.profiles[:p4]).to eq(Profile.new(load_balancing_policy: lbp2, + retry_policy: retry_policy, + consistency: :quorum, + timeout: 12)) end it 'should return unique list of lbps' do diff --git a/spec/cassandra/execution/profile_spec.rb b/spec/cassandra/execution/profile_spec.rb index 2898565a9..73ab026f8 100644 --- a/spec/cassandra/execution/profile_spec.rb +++ b/spec/cassandra/execution/profile_spec.rb @@ -89,11 +89,14 @@ module Execution end it 'should ignore all attributes from parent profile if it is fully specified itself' do - expect(profile.merge_from(default_profile)).to eq(profile) + expect(profile.merge_from(default_profile)).to be(profile) end it 'should respect nil timeout in parent' do - parent_profile = Profile.new(timeout: nil) + parent_profile = Profile.new(load_balancing_policy: lbp, + retry_policy: retry_policy, + consistency: :one, + timeout: nil) expect(empty_profile.timeout).to eq(:unspecified) expect(empty_profile.merge_from(parent_profile).timeout).to be_nil end diff --git a/spec/cassandra_spec.rb b/spec/cassandra_spec.rb index ed7b10234..856260ae2 100644 --- a/spec/cassandra_spec.rb +++ b/spec/cassandra_spec.rb @@ -41,6 +41,17 @@ def plan end end +class GoodRetryPolicy + def read_timeout + end + + def write_timeout + end + + def unavailable + end +end + describe Cassandra do context :validate_and_massage_options do let(:profiles) { @@ -61,6 +72,23 @@ def plan expect(C.validate(execution_profiles: profiles)).to eq({ execution_profiles: profiles }) expect { C.validate(execution_profiles: []) }.to raise_error(ArgumentError) end + + it 'should validate that execution profiles and load_balancing_policy are mutually exclusive' do + expect { C.validate(execution_profiles: profiles, load_balancing_policy: lbp) }.to raise_error(ArgumentError) + end + + it 'should validate that execution profiles and consistency are mutually exclusive' do + expect { C.validate(execution_profiles: profiles, consistency: :one) }.to raise_error(ArgumentError) + end + + it 'should validate that execution profiles and timeout are mutually exclusive' do + expect { C.validate(execution_profiles: profiles, timeout: 7) }.to raise_error(ArgumentError) + end + + it 'should validate that execution profiles and retry_policy are mutually exclusive' do + expect { C.validate(execution_profiles: profiles, retry_policy: GoodRetryPolicy.new) }. + to raise_error(ArgumentError) + end end context 'for username and password' do @@ -278,17 +306,7 @@ def schedule end it 'should validate :retry_policy option' do - class GoodPolicy - def read_timeout - end - - def write_timeout - end - - def unavailable - end - end - policy = GoodPolicy.new + policy = GoodRetryPolicy.new expect(C.validate(retry_policy: policy)).to eq({ retry_policy: policy }) expect { C.validate(retry_policy: 'junk') }.to raise_error(ArgumentError) expect { C.validate(retry_policy: nil) }.to raise_error(ArgumentError) From 4cdc4cd02b1cd394cac4ee2f737a11995556be8d Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Fri, 4 Nov 2016 12:29:42 -0700 Subject: [PATCH 172/196] RUBY-277: Execution Profile improvements in core driver * Fixed broken session-test. --- integration/session_test.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/integration/session_test.rb b/integration/session_test.rb index c2834d661..3253ab70a 100644 --- a/integration/session_test.rb +++ b/integration/session_test.rb @@ -939,7 +939,6 @@ def test_can_use_execution_profiles assert_equal profile_1, execution_profile_1 execution_profile_2 = cluster.execution_profiles[:profile_2] - assert_equal profile_2, execution_profile_2 assert_equal cluster.execution_profiles[:default], execution_profile_2 session = cluster.connect From a8093fe924d3051faa093c0c7c52b1449b782d2a Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Fri, 4 Nov 2016 17:44:02 -0700 Subject: [PATCH 173/196] RUBY-278 - Remove profile inheritance logic; use system defaults instead. --- features/basics/execution_profiles.feature | 12 ++-- integration/session_test.rb | 11 ++-- lib/cassandra/execution/profile.rb | 61 ++++++------------- lib/cassandra/execution/profile_manager.rb | 52 ++-------------- .../execution/profile_manager_spec.rb | 34 ++--------- spec/cassandra/execution/profile_spec.rb | 43 +++---------- 6 files changed, 49 insertions(+), 164 deletions(-) diff --git a/features/basics/execution_profiles.feature b/features/basics/execution_profiles.feature index 294999965..5e4a2f06d 100644 --- a/features/basics/execution_profiles.feature +++ b/features/basics/execution_profiles.feature @@ -68,17 +68,17 @@ Feature: Execution profiles """ There are 2 execution profiles in the cluster: - Name: default - Load_balancing_policy: Cassandra::LoadBalancing::Policies::TokenAware - Retry policy: Cassandra::Retry::Policies::Default - Consistency: local_one - Timeout: 12 - Name: my_profile Load_balancing_policy: Cassandra::LoadBalancing::Policies::RoundRobin Retry policy: Cassandra::Retry::Policies::DowngradingConsistency Consistency: all Timeout: 32 + + Name: default + Load_balancing_policy: Cassandra::LoadBalancing::Policies::TokenAware + Retry policy: Cassandra::Retry::Policies::Default + Consistency: local_one + Timeout: 12 """ Scenario: Configure different load balancing policies with profiles diff --git a/integration/session_test.rb b/integration/session_test.rb index 3253ab70a..63e8ee554 100644 --- a/integration/session_test.rb +++ b/integration/session_test.rb @@ -910,9 +910,9 @@ def test_can_retrieve_cluster_options # # test_can_use_execution_profiles tests that execution profiles can be created and used in session execution. It first # creates two execution profiles: one with all options specified and another with no options specified. It then - # creates a cluster with these two profiles and verifies their options. The 2nd execution profile should be equivalent - # to the :default execution profile as any missing options are copied over. It then executes a simple query using - # the execution profiles and verifies that the execution info shows their use. + # creates a cluster with these two profiles and verifies their options. The 2nd execution profile should use + # system defaults for attributes. It then executes a simple query using the execution profiles and verifies that the + # execution info shows their use. # # @since 3.1.0 # @jira_ticket RUBY-256 @@ -939,7 +939,10 @@ def test_can_use_execution_profiles assert_equal profile_1, execution_profile_1 execution_profile_2 = cluster.execution_profiles[:profile_2] - assert_equal cluster.execution_profiles[:default], execution_profile_2 + assert_equal cluster.execution_profiles[:default].timeout, execution_profile_2.timeout + assert_equal cluster.execution_profiles[:default].consistency, execution_profile_2.consistency + assert_instance_of Cassandra::Retry::Policies::Default, execution_profile_2.retry_policy + assert_instance_of Cassandra::LoadBalancing::Policies::TokenAware, execution_profile_2.load_balancing_policy session = cluster.connect exec_options = session.execute('select * from system.local').execution_info.options diff --git a/lib/cassandra/execution/profile.rb b/lib/cassandra/execution/profile.rb index 16a47eecd..3c3567e7a 100644 --- a/lib/cassandra/execution/profile.rb +++ b/lib/cassandra/execution/profile.rb @@ -43,44 +43,37 @@ class Profile attr_reader :timeout # @private - attr_accessor :parent_name + DEFAULT_OPTIONS = { consistency: :local_one, timeout: 12 }.freeze - # @private - DEFAULT_OPTIONS = {load_balancing_policy: :unspecified, - retry_policy: :unspecified, - consistency: :unspecified, - timeout: :unspecified}.freeze - - # @private - DEFAULT_PARENT_NAME = :default - - # @param options [Hash] hash of attributes. Unspecified attributes or attributes with nil values effectively - # fall back to the attributes in the default execution profile. - # @option options [Numeric] :timeout (:unspecified) Request execution timeout in + # @param options [Hash] hash of attributes. Unspecified attributes fall back to system defaults. + # @option options [Numeric] :timeout (12) Request execution timeout in # seconds. Setting value to `nil` will remove request timeout. - # @option options [Cassandra::LoadBalancing::Policy] :load_balancing_policy (:unspecified) Load-balancing policy + # @option options [Cassandra::LoadBalancing::Policy] :load_balancing_policy ( + # LoadBalancing::Policies::TokenAware(LoadBalancing::Policies::DCAwareRoundRobin)) Load-balancing policy # that determines which node will run the next statement. - # @option options [Cassandra::Retry::Policy] :retry_policy (:unspecified) Retry policy that determines how - # request retries should be handled for different failure modes. - # @option options [Symbol] :consistency (:unspecified) Consistency level with which to run statements. Must be one + # @option options [Cassandra::Retry::Policy] :retry_policy (Retry::Policies::Default) Retry policy that + # determines how request retries should be handled for different failure modes. + # @option options [Symbol] :consistency (:local_one) Consistency level with which to run statements. Must be one # of {Cassandra::CONSISTENCIES}. def initialize(options = {}) validate(options) options = DEFAULT_OPTIONS.merge(options) - @load_balancing_policy = options[:load_balancing_policy] - @retry_policy = options[:retry_policy] + @load_balancing_policy = options[:load_balancing_policy] || + LoadBalancing::Policies::TokenAware.new( + LoadBalancing::Policies::DCAwareRoundRobin.new, + true) + @retry_policy = options[:retry_policy] || Retry::Policies::Default.new @consistency = options[:consistency] @timeout = options[:timeout] - @parent_name = DEFAULT_PARENT_NAME end # @private def to_h { - load_balancing_policy: @load_balancing_policy, - retry_policy: @retry_policy, - consistency: @consistency, - timeout: @timeout + load_balancing_policy: @load_balancing_policy, + retry_policy: @retry_policy, + consistency: @consistency, + timeout: @timeout } end @@ -152,26 +145,6 @@ def validate(options) "#{consistency.inspect} given") end end - - # @private - def merge_from(parent_profile) - return self if well_formed? - - parent_hash = parent_profile.to_h - self_hash = to_h - self_hash.each do |key, value| - self_hash[key] = parent_hash[key] if value == :unspecified - end - Profile.new(self_hash) - end - - # @private - def well_formed? - !@load_balancing_policy.nil? && @load_balancing_policy != :unspecified && - !@retry_policy.nil? && @retry_policy != :unspecified && - !@consistency.nil? && @consistency != :unspecified && - @timeout != :unspecified - end end end end diff --git a/lib/cassandra/execution/profile_manager.rb b/lib/cassandra/execution/profile_manager.rb index adde89a2d..a0a4070f3 100644 --- a/lib/cassandra/execution/profile_manager.rb +++ b/lib/cassandra/execution/profile_manager.rb @@ -25,26 +25,16 @@ class ProfileManager def initialize(default_profile, profiles) # default_profile is the default profile that we constructed internally. However, the user can override it - # with their own :default profile, which may not be fully specified. See if we have such a profile and merge - # in the "system defaults" from the profile we generated. + # with their own :default profile. If that happens, ignore the internally generated default profile. - custom_default = profiles.delete(:default) - unless custom_default.nil? - default_profile = custom_default.merge_from(default_profile) - end - - # Walk through the profiles and fill them out with attributes from the default profile when they're not - # set. Also, save off all of the load-balancing policies for easy access. + profiles[:default] = default_profile unless profiles.key?(:default) + # Save off all of the load-balancing policies for easy access. @load_balancing_policies = Set.new - @load_balancing_policies << default_profile.load_balancing_policy - @profiles = {default: default_profile} - - @unready_profiles = {} - profiles.each do |name, profile| - add_profile(name, profile) + @load_balancing_policies << profile.load_balancing_policy end + @profiles = profiles end def default_profile @@ -67,44 +57,14 @@ def distance(host) # NOTE: It's only safe to call add_profile when setting up the cluster object. In particular, # this is only ok before calling Driver#connect. def add_profile(name, profile) - if !profile.well_formed? && @profiles.key?(profile.parent_name) - # This profile is ready to inherit attributes from its parent. - profile = profile.merge_from(@profiles[profile.parent_name]) - end - if profile.well_formed? - make_available(name, profile) - did_add = true - while did_add && !@unready_profiles.empty? - did_add = false - @unready_profiles.dup.each do |name, profile| - did_add = hydrate_profile(name, profile) - end - end - else - # This profile isn't ready to inherit its parent attributes yet - @unready_profiles[name] = profile - end - end - - def make_available(name, profile) @profiles[name] = profile @load_balancing_policies << profile.load_balancing_policy - @unready_profiles.delete(name) - end - - def hydrate_profile(name, profile) - if @profiles.key?(profile.parent_name) - profile = profile.merge_from(@profiles[profile.parent_name]) - make_available(name, profile) - return true - end - false end # @private def inspect "#<#{self.class.name}:0x#{object_id.to_s(16)} " \ - "profiles=#{@profiles.inspect}>" + "profiles=#{@profiles.inspect}>" end end end diff --git a/spec/cassandra/execution/profile_manager_spec.rb b/spec/cassandra/execution/profile_manager_spec.rb index 067ff6fa8..57351aafe 100644 --- a/spec/cassandra/execution/profile_manager_spec.rb +++ b/spec/cassandra/execution/profile_manager_spec.rb @@ -30,22 +30,15 @@ module Execution let(:profile1) { Profile.new(load_balancing_policy: lbp1) } let(:profile2) { Profile.new(load_balancing_policy: lbp2) } let(:profile3) { Profile.new(load_balancing_policy: lbp3) } - let(:profile4) { Profile.new } let(:profile5) { Profile.new(load_balancing_policy: lbp1) } - let(:profile6) { - # This profile's parent is *not* the default, but rather one of the other profiles. - p = Profile.new - p.parent_name = :p3 - p - } let(:default_profile) { Profile.new(load_balancing_policy: lbp1, retry_policy: retry_policy, consistency: :quorum, timeout: 12) } let(:subject) { - ProfileManager.new(default_profile, {p6: profile6, p1: profile1, p2: profile2, p3: profile3, p4: profile4, p5: profile5}) + ProfileManager.new(default_profile, {p1: profile1, p2: profile2, p3: profile3, p5: profile5}) } let(:subject_with_custom_default) { - ProfileManager.new(default_profile, {default: profile2, p4: profile4}) + ProfileManager.new(default_profile, {default: profile2}) } before do @@ -65,30 +58,13 @@ module Execution allow(retry_policy).to receive(:unavailable) end - it 'should fill out profiles with values from default profile' do - expect(subject.profiles[:p1]).to eq(Profile.new(load_balancing_policy: lbp1, retry_policy: retry_policy, - consistency: :quorum, timeout: 12)) - expect(subject.profiles[:p2]).to eq(Profile.new(load_balancing_policy: lbp2, retry_policy: retry_policy, - consistency: :quorum, timeout: 12)) - expect(subject.profiles[:p3]).to eq(Profile.new(load_balancing_policy: lbp3, retry_policy: retry_policy, - consistency: :quorum, timeout: 12)) - expect(subject.profiles[:p4]).to eq(Profile.new(load_balancing_policy: lbp1, retry_policy: retry_policy, - consistency: :quorum, timeout: 12)) - expect(subject.profiles[:p5]).to eq(Profile.new(load_balancing_policy: lbp1, retry_policy: retry_policy, - consistency: :quorum, timeout: 12)) - expect(subject.profiles[:p6]).to eq(subject.profiles[:p3]) - end - it 'should respect custom default profile' do - expect(subject_with_custom_default.profiles[:p4]).to eq(Profile.new(load_balancing_policy: lbp2, - retry_policy: retry_policy, - consistency: :quorum, - timeout: 12)) + expect(subject_with_custom_default.profiles[:default]).to be(profile2) end it 'should return unique list of lbps' do - lbps = Set.new([lbp1, lbp2, lbp3]) - expect(subject.load_balancing_policies).to eq(lbps) + expect(subject.load_balancing_policies.size).to eq(3) + expect(subject.load_balancing_policies.include?(lbp1)).to eq(true) end context :distance do diff --git a/spec/cassandra/execution/profile_spec.rb b/spec/cassandra/execution/profile_spec.rb index 73ab026f8..ecb652cd9 100644 --- a/spec/cassandra/execution/profile_spec.rb +++ b/spec/cassandra/execution/profile_spec.rb @@ -74,45 +74,18 @@ module Execution end expect { Profile.new(consistency: 'foo') }.to raise_error(ArgumentError) end - end - - context :merge_from do - let(:default_profile) { Profile.new(load_balancing_policy: lbp, retry_policy: retry_policy, consistency: :one, - timeout: 10)} - let(:profile) { - Profile.new(load_balancing_policy: lbp2, retry_policy: retry_policy2, consistency: :quorum, timeout: 23) - } - let(:empty_profile) { Profile.new } - - it 'should accept all attributes from parent profile if it has no attributes itself' do - expect(Profile.new.merge_from(default_profile)).to eq(default_profile) - end - it 'should ignore all attributes from parent profile if it is fully specified itself' do - expect(profile.merge_from(default_profile)).to be(profile) + it 'should fall back to system defaults for unspecified attributes' do + p = Profile.new + expect(p.load_balancing_policy).to be_a(LoadBalancing::Policies::TokenAware) + expect(p.retry_policy).to be_a(Retry::Policies::Default) + expect(p.timeout).to eq(12) + expect(p.consistency).to be(:local_one) end - it 'should respect nil timeout in parent' do - parent_profile = Profile.new(load_balancing_policy: lbp, - retry_policy: retry_policy, - consistency: :one, - timeout: nil) - expect(empty_profile.timeout).to eq(:unspecified) - expect(empty_profile.merge_from(parent_profile).timeout).to be_nil + it 'should support nil timeout' do + expect(Profile.new(timeout: nil).timeout).to be_nil end - - it 'should preserve its nil timeout when parent timeout is not nil' do - profile = Profile.new(timeout: nil) - expect(profile.timeout).to be_nil - expect(profile.merge_from(default_profile).timeout).to be_nil - end - end - - it 'should not be well-formed if it has unspecified attributes' do - expect(Profile.new(timeout: 7).well_formed?).to eq(false) - expect(Profile.new(load_balancing_policy: lbp2, timeout: 7).well_formed?).to eq(false) - expect(Profile.new(retry_policy: retry_policy, load_balancing_policy: lbp2, timeout: 7).well_formed?).to eq(false) - expect(Profile.new(consistency: :one, retry_policy: retry_policy, load_balancing_policy: lbp2, timeout: 7).well_formed?).to eq(true) end end end From cdc954d0824bfaab069e646197fcc923690480c5 Mon Sep 17 00:00:00 2001 From: Kishan Karunaratne Date: Mon, 7 Nov 2016 16:37:12 -0800 Subject: [PATCH 174/196] [RUBY-277] Integration tests and cucumber features for execution profile refinements --- features/basics/execution_profiles.feature | 14 ++-- integration/session_test.rb | 92 +++++++++++++++++++--- 2 files changed, 87 insertions(+), 19 deletions(-) diff --git a/features/basics/execution_profiles.feature b/features/basics/execution_profiles.feature index 5e4a2f06d..639bae268 100644 --- a/features/basics/execution_profiles.feature +++ b/features/basics/execution_profiles.feature @@ -5,30 +5,30 @@ Feature: Execution profiles name in calls to `Session.execute*`. Profile names should be strings or symbols. In this release, a profile encapsulates load-balancing policy, - retry-policy, consistency-level, and timeout primitive options. + retry-policy, consistency-level, and timeout primitive options. Execution profiles are immutable once created. If a user specifies simple options to `Cassandra.cluster`, the options mentioned above get stored in a default execution profile named `:default`. This execution profile is used by default by `Session.execute*` methods. - User-defined execution profiles fall back to values from the default profile for unspecified attributes. This allows - the user to override only the options she wants to set differently from the default profile. + User-defined execution profiles fall back to values from the system defaults for unspecified attributes. This allows + the user to override only the options she wants to set differently from the system defaults. If you declare execution profiles, it is illegal to also include the primitive options mentioned above: ```ruby puts "This is bad" cluster = Cassandra.cluster(timeout: 7, execution_profiles: { - myprof: Cassandra::Execution::Profile.new(...) + my_profile: Cassandra::Execution::Profile.new(...) }) ``` - To change default execution profile attributes and declare other execution profiles, you must declare the :default - profile when initializing the cluster: + To change default execution profile attributes and also declare other execution profiles, you must explicitly declare + the :default profile when initializing the cluster: ```ruby puts "This is legal" cluster = Cassandra.cluster(execution_profiles: { default: Cassandra::Execution::Profile.new(timeout: 7) - myprof: Cassandra::Execution::Profile.new(...) + my_profile: Cassandra::Execution::Profile.new(...) }) ``` diff --git a/integration/session_test.rb b/integration/session_test.rb index 63e8ee554..5039c1cac 100644 --- a/integration/session_test.rb +++ b/integration/session_test.rb @@ -906,6 +906,61 @@ def test_can_retrieve_cluster_options cluster && cluster.close end + # Test for the default execution profile + # + # test_can_use_the_default_execution_profile tests that the default execution profile can be created and used. It + # first inspects the default execution profile for its options. It then creates a Cassandra cluster with some options, + # and verifies that these options are inserted into the default execution profile. It then creates an execution + # profile manually and specifies it as the default execution profile. Finally, it verifies that an ArgumentError + # is raised when execution profiles are used concurrently with a hash of options in cluster creation. + # + # @expected_errors [ArgumentError] When both execution profiles and options hash is specified in a cluster + # + # @since 3.1.0 + # @jira_ticket RUBY-277 + # @expected_result the default execution profiles should be defined and retrieved from the cluster object + # + # @test_category execution_profiles + # + def test_can_use_the_default_execution_profile + setup_schema + + # Default execution profile options + cluster = Cassandra.cluster + default_profile = cluster.execution_profiles[:default] + assert_instance_of Cassandra::LoadBalancing::Policies::TokenAware, default_profile.load_balancing_policy + assert_instance_of Cassandra::Retry::Policies::Default, default_profile.retry_policy + assert_equal :local_one, default_profile.consistency + assert_equal 12, default_profile.timeout + cluster.close + + # Default execution profile options changed automatically + cluster = Cassandra.cluster(timeout: 60, consistency: :all) + default_profile = cluster.execution_profiles[:default] + assert_instance_of Cassandra::LoadBalancing::Policies::TokenAware, default_profile.load_balancing_policy + assert_instance_of Cassandra::Retry::Policies::Default, default_profile.retry_policy + assert_equal :all, default_profile.consistency + assert_equal 60, default_profile.timeout + cluster.close + + # Manually specifying options in default execution profile + profile = Cassandra::Execution::Profile.new(timeout: 60, consistency: :all) + assert_instance_of Cassandra::LoadBalancing::Policies::TokenAware, profile.load_balancing_policy + assert_instance_of Cassandra::Retry::Policies::Default, profile.retry_policy + assert_equal :all, profile.consistency + assert_equal 60, profile.timeout + + cluster = Cassandra.cluster(execution_profiles: {default: profile}) + default_profile = cluster.execution_profiles[:default] + assert_equal profile, default_profile + cluster.close + + # Can't mix and match options with execution profile + assert_raises(ArgumentError) do + Cassandra.cluster(timeout: 60, execution_profiles: {default: profile}) + end + end + # Test for execution profile creation and use # # test_can_use_execution_profiles tests that execution profiles can be created and used in session execution. It first @@ -914,6 +969,8 @@ def test_can_retrieve_cluster_options # system defaults for attributes. It then executes a simple query using the execution profiles and verifies that the # execution info shows their use. # + # @expected_errors [NoMethodError] When execution profile is attempted to be modified + # # @since 3.1.0 # @jira_ticket RUBY-256 # @expected_result cluster execution profiles should be retrieved and used from the cluster object @@ -929,7 +986,21 @@ def test_can_use_execution_profiles timeout: 32 ) + assert_instance_of Cassandra::LoadBalancing::Policies::RoundRobin, profile_1.load_balancing_policy + assert_instance_of Cassandra::Retry::Policies::DowngradingConsistency, profile_1.retry_policy + assert_equal :all, profile_1.consistency + assert_equal 32, profile_1.timeout + + # Execution profiles are immutable + assert_raises(NoMethodError) do + profile_1.timeout = 5 + end + profile_2 = Cassandra::Execution::Profile.new + assert_instance_of Cassandra::LoadBalancing::Policies::TokenAware, profile_2.load_balancing_policy + assert_instance_of Cassandra::Retry::Policies::Default, profile_2.retry_policy + assert_equal :local_one, profile_2.consistency + assert_equal 12, profile_2.timeout profiles = {profile_1: profile_1, profile_2: profile_2} cluster = Cassandra.cluster(execution_profiles: profiles) @@ -939,23 +1010,20 @@ def test_can_use_execution_profiles assert_equal profile_1, execution_profile_1 execution_profile_2 = cluster.execution_profiles[:profile_2] - assert_equal cluster.execution_profiles[:default].timeout, execution_profile_2.timeout - assert_equal cluster.execution_profiles[:default].consistency, execution_profile_2.consistency - assert_instance_of Cassandra::Retry::Policies::Default, execution_profile_2.retry_policy - assert_instance_of Cassandra::LoadBalancing::Policies::TokenAware, execution_profile_2.load_balancing_policy + assert_equal profile_2, execution_profile_2 session = cluster.connect - exec_options = session.execute('select * from system.local').execution_info.options - assert_equal Cassandra::LoadBalancing::Policies::TokenAware, exec_options.load_balancing_policy.class - assert_equal Cassandra::Retry::Policies::Default, exec_options.retry_policy.class - assert_equal :local_one, exec_options.consistency - assert_equal 12, exec_options.timeout - exec_options = session.execute('select * from system.local', execution_profile: :profile_1).execution_info.options - assert_equal Cassandra::LoadBalancing::Policies::RoundRobin, exec_options.load_balancing_policy.class - assert_equal Cassandra::Retry::Policies::DowngradingConsistency, exec_options.retry_policy.class + assert_instance_of Cassandra::LoadBalancing::Policies::RoundRobin, exec_options.load_balancing_policy + assert_instance_of Cassandra::Retry::Policies::DowngradingConsistency, exec_options.retry_policy assert_equal :all, exec_options.consistency assert_equal 32, exec_options.timeout + + exec_options = session.execute('select * from system.local').execution_info.options + assert_instance_of Cassandra::LoadBalancing::Policies::TokenAware, exec_options.load_balancing_policy + assert_instance_of Cassandra::Retry::Policies::Default, exec_options.retry_policy + assert_equal :local_one, exec_options.consistency + assert_equal 12, exec_options.timeout ensure cluster && cluster.close end From 16d32dc7b0b1111c7ec5be718d66ff4d8bb037ed Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Wed, 9 Nov 2016 13:37:36 -0800 Subject: [PATCH 175/196] Doc updates --- features/basics/execution_profiles.feature | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/features/basics/execution_profiles.feature b/features/basics/execution_profiles.feature index 639bae268..e8fadb416 100644 --- a/features/basics/execution_profiles.feature +++ b/features/basics/execution_profiles.feature @@ -7,10 +7,18 @@ Feature: Execution profiles Profile names should be strings or symbols. In this release, a profile encapsulates load-balancing policy, retry-policy, consistency-level, and timeout primitive options. Execution profiles are immutable once created. - If a user specifies simple options to `Cassandra.cluster`, the options mentioned above get stored in a default + If a user specifies simple options to `Cassandra.cluster`, the options mentioned above are stored in a default execution profile named `:default`. This execution profile is used by default by `Session.execute*` methods. - User-defined execution profiles fall back to values from the system defaults for unspecified attributes. This allows - the user to override only the options she wants to set differently from the system defaults. + User-defined execution profiles fall back to the same system defaults that past versions of the driver fell back to + for unspecified options: + + * load_balancing_policy: `LoadBalancing::Policies::TokenAware.new(LoadBalancing::Policies::DCAwareRoundRobin.new, true)` + * retry-policy: `Retry::Policies::Default.new` + * consistency: `:local_one` + * timeout: `12` + + In particular, note that user-defined execution profiles do not fall back to options specified in a (possibly + user-defined) `:default` profile. If you declare execution profiles, it is illegal to also include the primitive options mentioned above: @@ -32,7 +40,7 @@ Feature: Execution profiles }) ``` - Unspecified attributes fall back to the same system defaults that have been used in past versions of the driver. + Unspecified attributes fall back to the system defaults mentioned above. Finally, options specified to `Session.execute*` methods override options specified in the desired execution profile. From 1e54ee1811ca12408ce6d2512117fac55aaa5ea5 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 14 Nov 2016 10:28:25 -0800 Subject: [PATCH 176/196] Prep for 3.1.0 release. --- CHANGELOG.md | 8 +++++--- Gemfile.lock | 2 +- README.md | 8 ++++---- lib/cassandra/version.rb | 2 +- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 565201aeb..39bf5a9f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,12 @@ -# master +# 3.1.0 Features: * Do not mark a host as down if there are active connections. * Update Keyspace metadata to include collection of indexes defined in the keyspace. -* Update Table metadata to include trigger-collection and view-collection metadata. +* Update Table metadata to include trigger-collection and view-collection metadata. Also include the cdc attribute, + introduced in C* 3.8. More details [here.](http://cassandra.apache.org/doc/latest/operating/cdc.html) * Added execution profiles to encapsulate a group of request execution options. -* Added support for v5 beta protocol. +* Added support for v5 beta protocol. This will always be a "work-in-progress" since the protocol is under + development and the driver is not necessarily updated to the latest revision of it. * Make prepared statement cache not be scoped by host and optimistically execute prepared statements on hosts where we are not sure the statement is already prepared. The motivation is that in the steady state, all nodes have prepared statements already, so there is no need to prepare statements before executing them. If the guess is wrong, diff --git a/Gemfile.lock b/Gemfile.lock index 97274895a..83c7cddac 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - cassandra-driver (3.1.0.rc.1) + cassandra-driver (3.1.0) ione (~> 1.2) GEM diff --git a/README.md b/README.md index 064988ff6..866e196dd 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,7 @@ __Note__: if you want to use compression you should also install [snappy](http:/ 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) +interface parity with cql-rb](https://github.com/datastax/ruby-driver/blob/v3.1.0/examples/cql-rb-wrapper.rb) to assist you with gradual upgrade. ## What's new in v3.1 @@ -110,7 +110,7 @@ Execution profiles allow you to group various execution options into a 'profile' profile at execution time. Get the scoop [here](http://docs.datastax.com/en/developer/ruby-driver/3.1/features/basics/execution_profiles). -See the [changelog](https://github.com/datastax/ruby-driver/blob/master/CHANGELOG.md) for more information on all +See the [changelog](https://github.com/datastax/ruby-driver/blob/v3.1.0/CHANGELOG.md) for more information on all changes in this version and past versions. ## What's new in v3.0 @@ -179,7 +179,7 @@ examples in the `features/` directory. ## Running tests If you don't feel like reading through the following instructions on how to run -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). +ruby-driver tests, feel free to [check out .travis.yml for the entire build code](https://github.com/datastax/ruby-driver/blob/v3.1.0/.travis.yml). * Check out the driver codebase and install test dependencies: @@ -203,7 +203,7 @@ CASSANDRA_VERSION=2.0.17 bundle exec rake test # run both as well as integration ## Changelog & versioning Check out the [releases on GitHub](https://github.com/datastax/ruby-driver/releases) and -[changelog](https://github.com/datastax/ruby-driver/blob/master/CHANGELOG.md). Version +[changelog](https://github.com/datastax/ruby-driver/blob/v3.1.0/CHANGELOG.md). Version numbering follows the [semantic versioning](http://semver.org/) scheme. Private and experimental APIs, defined as whatever is not in the diff --git a/lib/cassandra/version.rb b/lib/cassandra/version.rb index 05496aaa4..8bdc36799 100644 --- a/lib/cassandra/version.rb +++ b/lib/cassandra/version.rb @@ -17,5 +17,5 @@ #++ module Cassandra - VERSION = '3.1.0.rc.1'.freeze + VERSION = '3.1.0'.freeze end From d52d7789be392b9390b04855ecb7bb5015af87a8 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 14 Nov 2016 10:30:49 -0800 Subject: [PATCH 177/196] Update docs.yaml to have an entry for 3.1 version of docs. --- docs.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs.yaml b/docs.yaml index 706f5e07a..61f2439f2 100644 --- a/docs.yaml +++ b/docs.yaml @@ -39,6 +39,8 @@ links: - title: Releases href: https://github.com/datastax/ruby-driver/releases versions: + - name: 3.1 + ref: v3.1.0 - name: 3.0 ref: v3.0.3 - name: 2.1 From bba48b757da9c9fbe7363a57a57941ae9d81dd22 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 14 Nov 2016 10:57:42 -0800 Subject: [PATCH 178/196] * Added rewrite rule for removing "supplemental" url elements to docs.yaml * Rewrite 'whatsNew.html' refs. --- docs.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs.yaml b/docs.yaml index 61f2439f2..ca70a3536 100644 --- a/docs.yaml +++ b/docs.yaml @@ -18,6 +18,8 @@ sections: rewrites: - api/: api/cassandra/ - cassandra/cassandra: cassandra + - supplemental/: / + - en/latest-ruby-driver/ruby-driver/whatsNew.html: en/developer/ruby-driver/latest - 'trunk/doc/native_protocol_v([12])\.spec': cassandra-2.2/doc/native_protocol_v\1.spec - search: http://www.datastax.com/documentation/cql/3.1/webhelp/index.html replace: http://docs.datastax.com/en/cql/3.3/cql/cql_using/useAboutCQL.html From f7aa2f5b4d8b06c91f5e9b5b282b19fcbd1a3cef Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 14 Nov 2016 15:50:50 -0800 Subject: [PATCH 179/196] * Add a new section in CHANGELOG for the next version of the driver * Update version now that 3.1.0 is out. * Restore blob/master refs in README.md --- CHANGELOG.md | 6 ++++++ Gemfile.lock | 4 ++-- README.md | 8 ++++---- lib/cassandra/version.rb | 2 +- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39bf5a9f9..d5d615b57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +master + +Features: + +Bug Fixes: + # 3.1.0 Features: * Do not mark a host as down if there are active connections. diff --git a/Gemfile.lock b/Gemfile.lock index 83c7cddac..9c34471bf 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - cassandra-driver (3.1.0) + cassandra-driver (3.1.1.rc.1) ione (~> 1.2) GEM @@ -40,7 +40,7 @@ GEM ffi (1.9.10) ffi (1.9.10-java) gherkin (3.2.0) - ione (1.2.3) + ione (1.2.4) json (1.8.3) json (1.8.3-java) lz4-ruby (0.3.3) diff --git a/README.md b/README.md index 866e196dd..064988ff6 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,7 @@ __Note__: if you want to use compression you should also install [snappy](http:/ 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.1.0/examples/cql-rb-wrapper.rb) +interface parity with cql-rb](https://github.com/datastax/ruby-driver/blob/master/examples/cql-rb-wrapper.rb) to assist you with gradual upgrade. ## What's new in v3.1 @@ -110,7 +110,7 @@ Execution profiles allow you to group various execution options into a 'profile' profile at execution time. Get the scoop [here](http://docs.datastax.com/en/developer/ruby-driver/3.1/features/basics/execution_profiles). -See the [changelog](https://github.com/datastax/ruby-driver/blob/v3.1.0/CHANGELOG.md) for more information on all +See the [changelog](https://github.com/datastax/ruby-driver/blob/master/CHANGELOG.md) for more information on all changes in this version and past versions. ## What's new in v3.0 @@ -179,7 +179,7 @@ examples in the `features/` directory. ## Running tests If you don't feel like reading through the following instructions on how to run -ruby-driver tests, feel free to [check out .travis.yml for the entire build code](https://github.com/datastax/ruby-driver/blob/v3.1.0/.travis.yml). +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). * Check out the driver codebase and install test dependencies: @@ -203,7 +203,7 @@ CASSANDRA_VERSION=2.0.17 bundle exec rake test # run both as well as integration ## Changelog & versioning Check out the [releases on GitHub](https://github.com/datastax/ruby-driver/releases) and -[changelog](https://github.com/datastax/ruby-driver/blob/v3.1.0/CHANGELOG.md). Version +[changelog](https://github.com/datastax/ruby-driver/blob/master/CHANGELOG.md). Version numbering follows the [semantic versioning](http://semver.org/) scheme. Private and experimental APIs, defined as whatever is not in the diff --git a/lib/cassandra/version.rb b/lib/cassandra/version.rb index 8bdc36799..623de83ac 100644 --- a/lib/cassandra/version.rb +++ b/lib/cassandra/version.rb @@ -17,5 +17,5 @@ #++ module Cassandra - VERSION = '3.1.0'.freeze + VERSION = '3.1.1.rc.1'.freeze end From 13ea1f224ee7b193f274ba5c60aa6bfdc65e7dac Mon Sep 17 00:00:00 2001 From: Kishan Karunaratne Date: Wed, 7 Dec 2016 14:23:05 -0800 Subject: [PATCH 180/196] Update Cassandra runtimes in build.yaml --- build.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/build.yaml b/build.yaml index 1422de55d..166cd2a41 100644 --- a/build.yaml +++ b/build.yaml @@ -4,7 +4,7 @@ schedules: matrix: exclude: - ruby: ['2.2', 'jruby1.7'] - - cassandra: ['2.0', '2.2', '3.0'] + - cassandra: ['2.2', '3.0'] nightly: schedule: nightly branches: @@ -16,7 +16,6 @@ ruby: - jruby1.7 - jruby9k cassandra: - - 2.0 - 2.1 - 2.2 - 3.0 From 8ade71fd8e5760e2bd1ccad18cd5f7f503fc0c4f Mon Sep 17 00:00:00 2001 From: Michael Grosser Date: Wed, 12 Apr 2017 10:51:52 -0700 Subject: [PATCH 181/196] RUBY-292 replace failed connections to prevent ip for string errors --- lib/cassandra/cluster/client.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/cassandra/cluster/client.rb b/lib/cassandra/cluster/client.rb index f693eed2b..1db1da7dc 100644 --- a/lib/cassandra/cluster/client.rb +++ b/lib/cassandra/cluster/client.rb @@ -106,6 +106,16 @@ def connect failed_connections = connections.reject(&:connected?) + # convert Cassandra::Protocol::CqlProtocolHandler to something with a real host + failed_connections.map! do |c| + if c.host.is_a?(String) + host = @registry.each_host.detect { |h| h.ip.to_s == c.host } || raise("Unable to find host #{c.host}") + FailedConnection.new(c.error, host) + else + c + end + end + if failed_connections.size == connections.size errors = {} connections.each {|c| errors[c.host] = c.error unless c.error.nil?} From b670411fc67488d6a578b89faeeef48521f1d79e Mon Sep 17 00:00:00 2001 From: Kishan Karunaratne Date: Tue, 25 Apr 2017 12:18:34 -0700 Subject: [PATCH 182/196] Update CI --- build.yaml | 2 +- features/support/env.rb | 2 +- integration/client_error_test.rb | 4 ++-- integration/security/authentication_test.rb | 5 ++--- integration/security/ssl_authenticated_encryption_test.rb | 5 ++--- integration/security/ssl_encryption_test.rb | 7 +++---- 6 files changed, 11 insertions(+), 14 deletions(-) diff --git a/build.yaml b/build.yaml index 166cd2a41..bd72e5a01 100644 --- a/build.yaml +++ b/build.yaml @@ -19,7 +19,7 @@ cassandra: - 2.1 - 2.2 - 3.0 - - 3.9 + - '3.10' os: - ubuntu/trusty64 build: diff --git a/features/support/env.rb b/features/support/env.rb index b2421971b..2fcb914e2 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -74,7 +74,7 @@ def to_str end After('@ssl') do - @cluster.disable_ssl + CCM.remove_cluster(@cluster.name) end After('@netblock') do diff --git a/integration/client_error_test.rb b/integration/client_error_test.rb index 140c40e83..31c2292a4 100644 --- a/integration/client_error_test.rb +++ b/integration/client_error_test.rb @@ -75,7 +75,7 @@ def test_raise_error_on_write_failure # will have a map of instead of num-failures. When v5 is officially released, we # can remove the allow_beta_protocol arg in this test. cluster = Retry.with_attempts(5, Cassandra::Errors::NoHostsAvailable) { - Cassandra.cluster(allow_beta_protocol: true) + Cassandra.cluster } session = cluster.connect @@ -132,7 +132,7 @@ def test_raise_error_on_read_failure # will have a map of instead of num-failures. When v5 is officially released, we # can remove the allow_beta_protocol arg in this test. cluster = Retry.with_attempts(5, Cassandra::Errors::NoHostsAvailable) { - Cassandra.cluster(allow_beta_protocol: true) + Cassandra.cluster } session = cluster.connect diff --git a/integration/security/authentication_test.rb b/integration/security/authentication_test.rb index b769c8c49..0f46469d3 100644 --- a/integration/security/authentication_test.rb +++ b/integration/security/authentication_test.rb @@ -25,8 +25,7 @@ def self.before_suite end def self.after_suite - @@ccm_cluster && @@ccm_cluster.disable_authentication - super + CCM.remove_cluster(@@ccm_cluster.name) end # Test for basic successful authentication @@ -54,7 +53,7 @@ def test_can_authenticate_to_cluster refute_nil cluster ensure - cluster.close + cluster && cluster.close end # Test for basic unsuccessful authentication diff --git a/integration/security/ssl_authenticated_encryption_test.rb b/integration/security/ssl_authenticated_encryption_test.rb index ea678dbd2..c425b2ce6 100644 --- a/integration/security/ssl_authenticated_encryption_test.rb +++ b/integration/security/ssl_authenticated_encryption_test.rb @@ -25,8 +25,7 @@ def self.before_suite end def self.after_suite - @@ccm_cluster && @@ccm_cluster.disable_ssl - super + CCM.remove_cluster(@@ccm_cluster.name) end def test_can_connect_with_ssl_authentication @@ -38,7 +37,7 @@ def test_can_connect_with_ssl_authentication ) refute_nil cluster ensure - cluster.close + cluster && cluster.close end def test_raise_error_on_invalid_ssl_auth diff --git a/integration/security/ssl_encryption_test.rb b/integration/security/ssl_encryption_test.rb index f84fc8434..c18b98072 100644 --- a/integration/security/ssl_encryption_test.rb +++ b/integration/security/ssl_encryption_test.rb @@ -25,15 +25,14 @@ def self.before_suite end def self.after_suite - @@ccm_cluster && @@ccm_cluster.disable_ssl - super + CCM.remove_cluster(@@ccm_cluster.name) end def test_can_connect_with_default_ssl cluster = Cassandra.cluster(ssl: true) refute_nil cluster ensure - cluster.close + cluster && cluster.close end def test_raise_error_when_not_using_ssl @@ -47,7 +46,7 @@ def test_can_connect_with_ssl_ca cluster = Cassandra.cluster(server_cert: @@server_cert) refute_nil cluster ensure - cluster.close + cluster && cluster.close end def test_raise_error_on_invalid_ca_provided From b017d1dc159d1e03d7c3af5c9ecbd451caf06999 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Tue, 9 May 2017 13:57:55 -0700 Subject: [PATCH 183/196] RUBY-295 - Custom Address Resolution Feature fails * added address resolution calls in various places as appropriate. * Updated spec --- lib/cassandra/cluster/control_connection.rb | 27 ++++++--- lib/cassandra/cluster/registry.rb | 13 ++++ .../cluster/control_connection_spec.rb | 60 +++++++++++++------ 3 files changed, 74 insertions(+), 26 deletions(-) diff --git a/lib/cassandra/cluster/control_connection.rb b/lib/cassandra/cluster/control_connection.rb index f33e31ab2..08a71354d 100644 --- a/lib/cassandra/cluster/control_connection.rb +++ b/lib/cassandra/cluster/control_connection.rb @@ -225,12 +225,11 @@ def register_async else case event.change when 'UP' - address = event.address - + address = @address_resolver.resolve(event.address) if @registry.has_host?(address) @registry.host_up(address) else - refresh_host_async_maybe_retry(address) + refresh_host_async_maybe_retry(event.address) refresh_schema_async_wrapper end when 'DOWN' @@ -238,14 +237,14 @@ def register_async # logic in connector.rb to call host_down when all connections to a node are lost, # so that covers the requirement. when 'NEW_NODE' - address = event.address + address = @address_resolver.resolve(event.address) unless @registry.has_host?(address) - refresh_host_async_maybe_retry(address) + refresh_host_async_maybe_retry(event.address) refresh_schema_async_wrapper end when 'REMOVED_NODE' - @registry.host_lost(event.address) + @registry.host_lost(@address_resolver.resolve(event.address)) refresh_schema_async_wrapper end end @@ -480,7 +479,7 @@ def refresh_metadata_async raise Errors::InternalError, "Unable to fetch connected host's metadata" if local.empty? data = local.first - @registry.host_found(IPAddr.new(connection.host), data) + @registry.host_found(@address_resolver.resolve(IPAddr.new(connection.host)), data) @metadata.update(data) @logger.info("Completed refreshing connected host's metadata") @@ -516,6 +515,9 @@ def refresh_host_status_with_retry(original_timer, host, schedule) end end + # @param address [IPAddr] address node address, as reported from a C* event. Thus it is *not* resolved + # relative to the client, but rather is the address that other nodes would use to communicate with + # this node. def refresh_host_async_maybe_retry(address) synchronize do return Ione::Future.resolved if @refreshing_hosts || @refreshing_host[address] @@ -539,6 +541,9 @@ def refresh_host_async_maybe_retry(address) end end + # @param address [IPAddr] address node address, as reported from a C* event. Thus it is *not* resolved + # relative to the client, but rather is the address that other nodes would use to communicate with + # this node. def refresh_host_async_retry(address, error, schedule) timeout = schedule.next @logger.info("Failed to refresh host #{address} (#{error.class.name}: " \ @@ -560,6 +565,9 @@ def refresh_host_async_retry(address, error, schedule) end end + # @param address [IPAddr] address node address, as reported from a C* event. Thus it is *not* resolved + # relative to the client, but rather is the address that other nodes would use to communicate with + # this node. def refresh_host_async(address) connection = @connection return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil? @@ -581,6 +589,11 @@ def refresh_host_async(address) raise Errors::InternalError, "Unable to find host metadata: #{ip}" if rows.empty? @logger.info("Completed refreshing host metadata: #{ip}") + address = if ip == connection.host + @address_resolver.resolve(address) + else + peer_ip(rows.first, connection.host) + end @registry.host_found(address, rows.first) self diff --git a/lib/cassandra/cluster/registry.rb b/lib/cassandra/cluster/registry.rb index a77abf2da..692bf9bc9 100644 --- a/lib/cassandra/cluster/registry.rb +++ b/lib/cassandra/cluster/registry.rb @@ -60,14 +60,18 @@ def each_host(&block) end alias hosts each_host + # @param address [IPAddr] resolved address of the node def host(address) @hosts[address.to_s] end + # @param address [IPAddr] resolved address of the node def has_host?(address) @hosts.key?(address.to_s) end + # @param address [IPAddr] resolved address of the node + # @param data [Hash] attributes of the host, typically a row from system.local or system.peers. def host_found(address, data = {}) ip = address.to_s host = @hosts[ip] @@ -108,6 +112,7 @@ def host_found(address, data = {}) self end + # @param address [IPAddr] resolved address of the node def host_down(address) ip = address.to_s host = @hosts[ip] @@ -125,6 +130,7 @@ def host_down(address) self end + # @param address [IPAddr] resolved address of the node def host_up(address) ip = address.to_s host = @hosts[ip] @@ -142,6 +148,7 @@ def host_up(address) self end + # @param address [IPAddr] resolved address of the node def host_lost(address) ip = address.to_s host = nil @@ -161,6 +168,8 @@ def host_lost(address) private + # @param ip [String] resolved address of the node + # @param data [Hash] attributes of the host, typically a row from system.local or system.peers. def create_host(ip, data) Host.new(ip, data['host_id'], @@ -173,6 +182,7 @@ def create_host(ip, data) data['listen_address']) end + # @param ip [Host] host that is found to be up. def toggle_up(host) host = Host.new(host.ip, host.id, @@ -194,6 +204,7 @@ def toggle_up(host) host end + # @param ip [Host] host that is found to be down. def toggle_down(host) host = Host.new(host.ip, host.id, @@ -215,6 +226,7 @@ def toggle_down(host) host end + # @param ip [Host] host that is lost. def notify_lost(host) if host.up? @logger.debug("Host #{host.ip} is down and lost") @@ -251,6 +263,7 @@ def notify_lost(host) end end + # @param ip [Host] host that is found. def notify_found(host) @logger.debug("Host #{host.ip} is found and up") @listeners.each do |listener| diff --git a/spec/cassandra/cluster/control_connection_spec.rb b/spec/cassandra/cluster/control_connection_spec.rb index 70782250e..15fd1d446 100644 --- a/spec/cassandra/cluster/control_connection_spec.rb +++ b/spec/cassandra/cluster/control_connection_spec.rb @@ -63,6 +63,8 @@ class Cluster let(:schema_fetcher) { driver.schema_fetcher } + let(:local_ip) { ::IPAddr.new('127.0.0.1') } + def connections io_reactor.connections end @@ -181,9 +183,11 @@ def handle_request(&handler) ip = $1 rows = [ { + 'peer' => ip, 'rack' => racks[ip], 'data_center' => data_centers[ip], 'host_id' => host_ids[ip], + 'rpc_address' => bind_all_rpc_addresses ? ::IPAddr.new('0.0.0.0') : ip, 'release_version' => release_versions[ip], 'tokens' => tokens[ip] } @@ -334,6 +338,7 @@ def handle_request(&handler) it 'skips empty peers' do additional_rpc_addresses = additional_nodes.dup + expect(address_resolution_policy).to receive(:resolve).with(local_ip).and_return(local_ip) expect(address_resolution_policy).to receive(:resolve).with(additional_rpc_addresses[0]).and_return(additional_rpc_addresses[0]) expect(address_resolution_policy).to receive(:resolve).with(additional_rpc_addresses[1]).and_return(additional_rpc_addresses[1]) @@ -342,7 +347,7 @@ def handle_request(&handler) # We should give up on a peer if 'peer' is empty. This is indicated by *not* doing # an address resolution of rpc-address. - expect(address_resolution_policy).to_not receive(:resolve).with('127.1.2.3') + expect(address_resolution_policy).to_not receive(:resolve).with(::IPAddr.new('127.1.2.3')) handle_request do |request| case request @@ -386,15 +391,16 @@ def handle_request(&handler) it 'skips empty rack' do additional_rpc_addresses = additional_nodes.dup + expect(address_resolution_policy).to receive(:resolve).with(local_ip).and_return(local_ip) expect(address_resolution_policy).to receive(:resolve).with(additional_rpc_addresses[0]).and_return(additional_rpc_addresses[0]) expect(address_resolution_policy).to receive(:resolve).with(additional_rpc_addresses[1]).and_return(additional_rpc_addresses[1]) # RUBY-255: We should never try to do address resolution of nil. expect(address_resolution_policy).to_not receive(:resolve).with(nil) - # We should give up on a peer if 'rpc-address' is empty. This is indicated by *not* doing + # We should give up on a peer if 'rack' is empty. This is indicated by *not* doing # an address resolution of peer. - expect(address_resolution_policy).to_not receive(:resolve).with('127.1.2.3') + expect(address_resolution_policy).to_not receive(:resolve).with(::IPAddr.new('127.1.2.3')) handle_request do |request| case request @@ -408,7 +414,7 @@ def handle_request(&handler) 'rack' => racks[ip], 'data_center' => data_centers[ip], 'host_id' => host_ids[ip], - 'rpc_address' => bind_all_rpc_addresses ? IPAddr.new('0.0.0.0') : ip, + 'rpc_address' => bind_all_rpc_addresses ? ::IPAddr.new('0.0.0.0') : ip, 'release_version' => release_versions[ip], 'tokens' => tokens[ip] } @@ -438,15 +444,16 @@ def handle_request(&handler) it 'skips empty data_center' do additional_rpc_addresses = additional_nodes.dup + expect(address_resolution_policy).to receive(:resolve).with(local_ip).and_return(local_ip) expect(address_resolution_policy).to receive(:resolve).with(additional_rpc_addresses[0]).and_return(additional_rpc_addresses[0]) expect(address_resolution_policy).to receive(:resolve).with(additional_rpc_addresses[1]).and_return(additional_rpc_addresses[1]) # RUBY-255: We should never try to do address resolution of nil. expect(address_resolution_policy).to_not receive(:resolve).with(nil) - # We should give up on a peer if 'rpc-address' is empty. This is indicated by *not* doing + # We should give up on a peer if 'datacenter' is empty. This is indicated by *not* doing # an address resolution of peer. - expect(address_resolution_policy).to_not receive(:resolve).with('127.1.2.3') + expect(address_resolution_policy).to_not receive(:resolve).with(::IPAddr.new('127.1.2.3')) handle_request do |request| case request @@ -460,7 +467,7 @@ def handle_request(&handler) 'rack' => racks[ip], 'data_center' => data_centers[ip], 'host_id' => host_ids[ip], - 'rpc_address' => bind_all_rpc_addresses ? IPAddr.new('0.0.0.0') : ip, + 'rpc_address' => bind_all_rpc_addresses ? ::IPAddr.new('0.0.0.0') : ip, 'release_version' => release_versions[ip], 'tokens' => tokens[ip] } @@ -490,15 +497,16 @@ def handle_request(&handler) it 'skips empty host_id' do additional_rpc_addresses = additional_nodes.dup + expect(address_resolution_policy).to receive(:resolve).with(local_ip).and_return(local_ip) expect(address_resolution_policy).to receive(:resolve).with(additional_rpc_addresses[0]).and_return(additional_rpc_addresses[0]) expect(address_resolution_policy).to receive(:resolve).with(additional_rpc_addresses[1]).and_return(additional_rpc_addresses[1]) # RUBY-255: We should never try to do address resolution of nil. expect(address_resolution_policy).to_not receive(:resolve).with(nil) - # We should give up on a peer if 'rpc-address' is empty. This is indicated by *not* doing + # We should give up on a peer if 'host_id' is empty. This is indicated by *not* doing # an address resolution of peer. - expect(address_resolution_policy).to_not receive(:resolve).with('127.1.2.3') + expect(address_resolution_policy).to_not receive(:resolve).with(::IPAddr.new('127.1.2.3')) handle_request do |request| case request @@ -512,7 +520,7 @@ def handle_request(&handler) 'rack' => racks[ip], 'data_center' => data_centers[ip], 'host_id' => host_ids[ip], - 'rpc_address' => bind_all_rpc_addresses ? IPAddr.new('0.0.0.0') : ip, + 'rpc_address' => bind_all_rpc_addresses ? ::IPAddr.new('0.0.0.0') : ip, 'release_version' => release_versions[ip], 'tokens' => tokens[ip] } @@ -542,6 +550,7 @@ def handle_request(&handler) it 'skips empty rpc_address' do additional_rpc_addresses = additional_nodes.dup + expect(address_resolution_policy).to receive(:resolve).with(local_ip).and_return(local_ip) expect(address_resolution_policy).to receive(:resolve).with(additional_rpc_addresses[0]).and_return(additional_rpc_addresses[0]) expect(address_resolution_policy).to receive(:resolve).with(additional_rpc_addresses[1]).and_return(additional_rpc_addresses[1]) @@ -550,7 +559,7 @@ def handle_request(&handler) # We should give up on a peer if 'rpc-address' is empty. This is indicated by *not* doing # an address resolution of peer. - expect(address_resolution_policy).to_not receive(:resolve).with('127.1.2.3') + expect(address_resolution_policy).to_not receive(:resolve).with(::IPAddr.new('127.1.2.3')) handle_request do |request| case request @@ -594,6 +603,7 @@ def handle_request(&handler) it 'skips empty tokens' do additional_rpc_addresses = additional_nodes.dup + expect(address_resolution_policy).to receive(:resolve).with(local_ip).and_return(local_ip) expect(address_resolution_policy).to receive(:resolve).with(additional_rpc_addresses[0]).and_return(additional_rpc_addresses[0]) expect(address_resolution_policy).to receive(:resolve).with(additional_rpc_addresses[1]).and_return(additional_rpc_addresses[1]) @@ -602,7 +612,7 @@ def handle_request(&handler) # We should give up on a peer if 'rpc-address' is empty. This is indicated by *not* doing # an address resolution of peer. - expect(address_resolution_policy).to_not receive(:resolve).with('127.1.2.3') + expect(address_resolution_policy).to_not receive(:resolve).with(::IPAddr.new('127.1.2.3')) handle_request do |request| case request @@ -646,6 +656,7 @@ def handle_request(&handler) it 'skips matching peer' do additional_rpc_addresses = additional_nodes.dup + expect(address_resolution_policy).to receive(:resolve).with(local_ip).and_return(local_ip) expect(address_resolution_policy).to receive(:resolve).with(additional_rpc_addresses[0]).and_return(additional_rpc_addresses[0]) expect(address_resolution_policy).to receive(:resolve).with(additional_rpc_addresses[1]).and_return(additional_rpc_addresses[1]) @@ -654,8 +665,8 @@ def handle_request(&handler) # We should give up on a peer if its rpc_address is the local host. This is indicated by *not* doing # an address resolution of it. - expect(address_resolution_policy).to_not receive(:resolve).with('127.0.0.9') - expect(address_resolution_policy).to_not receive(:resolve).with('127.0.0.1') + expect(address_resolution_policy).to_not receive(:resolve).with(::IPAddr.new('127.0.0.9')) + expect(address_resolution_policy).to_not receive(:resolve).with(::IPAddr.new('127.0.0.1')) handle_request do |request| case request @@ -669,7 +680,7 @@ def handle_request(&handler) 'rack' => racks[ip], 'data_center' => data_centers[ip], 'host_id' => host_ids[ip], - 'rpc_address' => bind_all_rpc_addresses ? IPAddr.new('0.0.0.0') : ip, + 'rpc_address' => bind_all_rpc_addresses ? ::IPAddr.new('0.0.0.0') : ip, 'release_version' => release_versions[ip], 'tokens' => tokens[ip] } @@ -680,7 +691,7 @@ def handle_request(&handler) 'rack' => racks['127.0.0.9'], 'data_center' => data_centers['127.0.0.9'], 'host_id' => host_ids['127.0.0.9'], - 'rpc_address' => bind_all_rpc_addresses ? IPAddr.new('0.0.0.0') : '127.0.0.9', + 'rpc_address' => bind_all_rpc_addresses ? ::IPAddr.new('0.0.0.0') : '127.0.0.9', 'release_version' => release_versions['127.0.0.9'], 'tokens' => tokens['127.0.0.9'] } @@ -697,6 +708,7 @@ def handle_request(&handler) it 'skips matching rpc_address' do additional_rpc_addresses = additional_nodes.dup + expect(address_resolution_policy).to receive(:resolve).with(local_ip).and_return(local_ip) expect(address_resolution_policy).to receive(:resolve).with(additional_rpc_addresses[0]).and_return(additional_rpc_addresses[0]) expect(address_resolution_policy).to receive(:resolve).with(additional_rpc_addresses[1]).and_return(additional_rpc_addresses[1]) @@ -705,8 +717,7 @@ def handle_request(&handler) # We should give up on a peer if its rpc_address is the local host. This is indicated by *not* doing # an address resolution of it. - expect(address_resolution_policy).to_not receive(:resolve).with('127.0.0.9') - expect(address_resolution_policy).to_not receive(:resolve).with('127.0.0.1') + expect(address_resolution_policy).to_not receive(:resolve).with(::IPAddr.new('127.0.0.9')) handle_request do |request| case request @@ -720,7 +731,7 @@ def handle_request(&handler) 'rack' => racks[ip], 'data_center' => data_centers[ip], 'host_id' => host_ids[ip], - 'rpc_address' => bind_all_rpc_addresses ? IPAddr.new('0.0.0.0') : ip, + 'rpc_address' => bind_all_rpc_addresses ? ::IPAddr.new('0.0.0.0') : ip, 'release_version' => release_versions[ip], 'tokens' => tokens[ip] } @@ -867,10 +878,13 @@ def handle_request(&handler) it 'refreshes metadata and notifies registry' do ip = address.to_s + expect(address_resolution_policy).to receive(:resolve).with(address).and_return(address).twice expect(cluster_registry).to receive(:host_found).once.with(address, { + 'peer' => address, 'rack' => racks[ip], 'data_center' => data_centers[ip], 'host_id' => host_ids[ip], + 'rpc_address' => address, 'release_version' => release_versions[ip], 'tokens' => tokens[ip] }) @@ -933,10 +947,13 @@ def handle_request(&handler) it 'notifies registry' do ip = address.to_s + expect(address_resolution_policy).to receive(:resolve).with(address).and_return(address).twice expect(cluster_registry).to receive(:host_found).once.with(address, { + 'peer' => address, 'rack' => racks[ip], 'data_center' => data_centers[ip], 'host_id' => host_ids[ip], + 'rpc_address' => address, 'release_version' => release_versions[ip], 'tokens' => tokens[ip] }) @@ -976,9 +993,11 @@ def handle_request(&handler) ip = $1 rows = [ { + 'peer' => ip, 'rack' => racks[ip], 'data_center' => data_centers[ip], 'host_id' => host_ids[ip], + 'rpc_address' => ip, 'release_version' => release_versions[ip], 'tokens' => tokens[ip] } @@ -999,10 +1018,13 @@ def handle_request(&handler) it 'tries again' do connections.first.trigger_event(event) ip = address.to_s + expect(address_resolution_policy).to receive(:resolve).with(address).and_return(address) expect(cluster_registry).to receive(:host_found).once.with(address, { + 'peer' => address, 'rack' => racks[ip], 'data_center' => data_centers[ip], 'host_id' => host_ids[ip], + 'rpc_address' => address, 'release_version' => release_versions[ip], 'tokens' => tokens[ip] }) From caba8218dfa9d989d92a80d0e1afc44dc0a105a0 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Tue, 9 May 2017 15:27:30 -0700 Subject: [PATCH 184/196] RUBY-291 - Driver fails to connect to cluster when a table column type has a quoted name. * Fixed defect in parsing schema metadata that contained quotes in column type names. * Break out cuke run into two pieces -- one for interactive cukes and one for non-interactive. A clean cuke run for just interactive features is more stable with jruby 9k. --- CHANGELOG.md | 3 +- Rakefile | 14 +++++++- cucumber.yml | 2 ++ .../automatic_reconnection.feature | 1 + integration/types/user_defined_type_test.rb | 33 +++++++++++++++++++ .../cluster/schema/cql_type_parser.rb | 2 +- 6 files changed, 52 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5d615b57..8b8f38612 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,9 @@ -master +# 3.1.1 Features: Bug Fixes: +* [RUBY-291](https://datastax-oss.atlassian.net/browse/RUBY-291) Driver fails to connect to cluster when a table column type has a quoted name. # 3.1.0 Features: diff --git a/Rakefile b/Rakefile index 06c2dec43..da09c02a0 100644 --- a/Rakefile +++ b/Rakefile @@ -11,7 +11,19 @@ ENV['FAIL_FAST'] ||= 'Y' RSpec::Core::RakeTask.new(rspec: :compile) -Cucumber::Rake::Task.new(cucumber: :compile) +# We separate interactive from non-interactive features because jruby 9k sometimes has trouble +# closing its pipe to the child process in interactive features. + +Cucumber::Rake::Task.new({cucumber_interactive: :compile}, 'Run cucumber features that are interactive') do |t| + t.profile = 'interactive' +end + +Cucumber::Rake::Task.new({cucumber_noninteractive: :compile}, 'Run cucumber features that are non-interactive') do |t| + t.profile = 'non_interactive' +end + +desc 'Run cucumber features' +task cucumber: [:cucumber_noninteractive, :cucumber_interactive] desc 'Run all tests' task test: [:rspec, :integration, :cucumber] diff --git a/cucumber.yml b/cucumber.yml index 83a98e36a..e4818fa0a 100644 --- a/cucumber.yml +++ b/cucumber.yml @@ -36,3 +36,5 @@ if cassandra_version.start_with?('1.2') end %> default: --color --guess --tags ~@todo --tags ~@cassandra-version-specific<%= cassandra_version_tags %> -r features/ +non_interactive: --color --guess --tags ~@interactive --tags ~@todo --tags ~@cassandra-version-specific<%= cassandra_version_tags %> -r features/ +interactive: --color --guess --tags @interactive --tags ~@todo --tags ~@cassandra-version-specific<%= cassandra_version_tags %> -r features/ diff --git a/features/reconnection/automatic_reconnection.feature b/features/reconnection/automatic_reconnection.feature index 504b7dcd4..c50164edf 100644 --- a/features/reconnection/automatic_reconnection.feature +++ b/features/reconnection/automatic_reconnection.feature @@ -100,6 +100,7 @@ Feature: Automatic reconnection And it is running interactively And I wait for its output to contain "START" + @interactive Scenario: Driver reconnects when all hosts are down When node 1 stops When node 2 stops diff --git a/integration/types/user_defined_type_test.rb b/integration/types/user_defined_type_test.rb index 73b9fe76d..4aedcbd6c 100644 --- a/integration/types/user_defined_type_test.rb +++ b/integration/types/user_defined_type_test.rb @@ -66,6 +66,39 @@ def test_can_insert_udts cluster && cluster.close end + def test_can_insert_udts_with_capitalized_name + skip("UDTs are only available in C* after 2.1") if CCM.cassandra_version < '2.1.0' + + cluster = Cassandra.cluster + session = cluster.connect("simplex") + + session.execute('CREATE TYPE "User" (age int, name text, gender text)') + session.execute('CREATE TABLE mytable (a int PRIMARY KEY, b frozen<"User">)') + + # RUBY-291 - We can't connect to the cluster when a UDT name contains capital letters and is thus quoted. + cluster2 = Cassandra.cluster + session2 = cluster2.connect("simplex") + + # Test non-prepared statement + session2.execute("INSERT INTO mytable (a, b) VALUES (0, {age: 30, name: 'John', gender: 'male'})") + user_value = session2.execute("SELECT b FROM mytable where a=0").first['b'] + assert_equal 30, user_value.age + assert_equal 'John', user_value.name + assert_equal 'male', user_value.gender + + # Test prepared statement + insert = Retry.with_attempts(5) { session2.prepare("INSERT INTO simplex.mytable (a, b) VALUES (?, ?)") } + Retry.with_attempts(5) { session2.execute(insert, arguments: [1, Cassandra::UDT.new(age: 25, name: 'Jane', gender: 'female')]) } + + user_value = session2.execute("SELECT b FROM mytable where a=1").first['b'] + assert_equal 25, user_value.age + assert_equal 'Jane', user_value.name + assert_equal 'female', user_value.gender + ensure + cluster && cluster.close + cluster2 && cluster2.close + end + def test_can_insert_same_udt_different_keyspaces skip("UDTs are only available in C* after 2.1") if CCM.cassandra_version < '2.1.0' diff --git a/lib/cassandra/cluster/schema/cql_type_parser.rb b/lib/cassandra/cluster/schema/cql_type_parser.rb index faf0cb37f..f6413e768 100644 --- a/lib/cassandra/cluster/schema/cql_type_parser.rb +++ b/lib/cassandra/cluster/schema/cql_type_parser.rb @@ -100,7 +100,7 @@ def parse_node(string) when ' ' next else - node.name << char + node.name << char unless char == '"' end end From 48c854cd780d0eda36f39957904d3ed0ebce772c Mon Sep 17 00:00:00 2001 From: Ville Lautanala Date: Wed, 10 May 2017 20:43:31 +0300 Subject: [PATCH 185/196] RUBY-294 - Ruby 2.4 compatible integer type guessing * Update JSON gem to 1.8.6 for Ruby 2.4 compatibility * Ruby 2.4 compatible integer type guessing Fixnum and Bignum classes were unified to a single integer class in Ruby 2.4. All integers in Ruby 2.4 would match to ::Fixnum. For proper type guessing, the size of the number needs to be inspected. Thanks @lautis for discovering and fixing this. --- Gemfile.lock | 6 ++--- lib/cassandra/util.rb | 3 +-- spec/cassandra/util_spec.rb | 48 +++++++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 spec/cassandra/util_spec.rb diff --git a/Gemfile.lock b/Gemfile.lock index 9c34471bf..bfc005571 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -41,8 +41,8 @@ GEM ffi (1.9.10-java) gherkin (3.2.0) ione (1.2.4) - json (1.8.3) - json (1.8.3-java) + json (1.8.6) + json (1.8.6-java) lz4-ruby (0.3.3) lz4-ruby (0.3.3-java) minitest (4.7.5) @@ -121,4 +121,4 @@ DEPENDENCIES yard BUNDLED WITH - 1.11.2 + 1.14.6 diff --git a/lib/cassandra/util.rb b/lib/cassandra/util.rb index a944e20f1..b1d79943a 100644 --- a/lib/cassandra/util.rb +++ b/lib/cassandra/util.rb @@ -174,9 +174,8 @@ def escape_name(name) def guess_type(object) case object when ::String then Types.varchar - when ::Fixnum then Types.bigint + when ::Integer then object.size > 8 ? Types.varint : Types.bigint when ::Float then Types.double - when ::Bignum then Types.varint when ::BigDecimal then Types.decimal when ::TrueClass then Types.boolean when ::FalseClass then Types.boolean diff --git a/spec/cassandra/util_spec.rb b/spec/cassandra/util_spec.rb new file mode 100644 index 000000000..a542b3ca1 --- /dev/null +++ b/spec/cassandra/util_spec.rb @@ -0,0 +1,48 @@ +# encoding: utf-8 + +#-- +# Copyright 2013-2016 DataStax, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + +require 'spec_helper' + +module Cassandra + describe Util do + describe '.guess_type' do + specs = [ + [Types.text, 'test'], + [Types.varint, 2**64], + [Types.bigint, 2**63], + [Types.boolean, true], + [Types.boolean, false], + [Types.decimal, ::BigDecimal.new('1042342234234.123423435647768234')], + [Types.double, 10000.123123123], + [Types.inet, ::IPAddr.new('8.8.8.8')], + [Types.uuid, Uuid.new(::SecureRandom.hex)], + [Types.uuid, TimeUuid.new(::SecureRandom.hex)], + [Types.timestamp, ::Time.now], + [Types.map(Types.text, Types.bigint), { 'text' => 0 }], + [Types.list(Types.varint), [2**64]], + [Types.set(Types.bigint), Set.new([0])], + ] + + specs.each do |expected_type, value| + it "returns #{expected_type} for #{value}" do + expect(Util.guess_type(value)).to eq(expected_type) + end + end + end + end +end From 1148dd4db7537815a9153ff62d5acf284d7bcf17 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Wed, 10 May 2017 12:19:33 -0700 Subject: [PATCH 186/196] RUBY-294 - Ruby 2.4 compatible integer type guessing * Add Ruby-2.4 to build.yaml, for Jenkins builds. * Fix broken cuke, since BigDecimal emits slightly differently between Ruby2.4 and prior Rubies. --- build.yaml | 1 + features/basics/datatypes.feature | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/build.yaml b/build.yaml index bd72e5a01..af62ccc29 100644 --- a/build.yaml +++ b/build.yaml @@ -13,6 +13,7 @@ schedules: ruby: - 2.2 - 2.3 + - 2.4 - jruby1.7 - jruby9k cassandra: diff --git a/features/basics/datatypes.feature b/features/basics/datatypes.feature index 94d6b46c4..1c1fbeb76 100644 --- a/features/basics/datatypes.feature +++ b/features/basics/datatypes.feature @@ -88,7 +88,11 @@ Feature: Datatypes row = session.execute("SELECT * FROM mytable").first puts "Bigint: #{row['b']}" - puts "Decimal: #{row['c']}" + + # Different versions of ruby express scientific notation with lowercase or capital E (E10 vs e10). + # Unify on upper-case + puts "Decimal: #{row['c'].to_s.upcase}" + puts "Double: #{row['d']}" puts "Float: #{row['e']}" puts "Integer: #{row['f']}" From 37326c58826037c42c5c093c6c4f10e1c2b782b9 Mon Sep 17 00:00:00 2001 From: Kishan Karunaratne Date: Thu, 11 May 2017 14:12:58 -0700 Subject: [PATCH 187/196] [RUBY-291] Integration tests for quoted UDTs --- integration/types/user_defined_type_test.rb | 27 +++++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/integration/types/user_defined_type_test.rb b/integration/types/user_defined_type_test.rb index 4aedcbd6c..49a6c0bde 100644 --- a/integration/types/user_defined_type_test.rb +++ b/integration/types/user_defined_type_test.rb @@ -66,31 +66,42 @@ def test_can_insert_udts cluster && cluster.close end - def test_can_insert_udts_with_capitalized_name - skip("UDTs are only available in C* after 2.1") if CCM.cassandra_version < '2.1.0' + # Test for inserting quoted udts + # + # test_can_insert_quoted_udts tests that udts with a quoted name can be inserted and retrieved. + # + # @since 3.2.0 + # @jira_ticket RUBY-291 + # @expected_result Quoted UDTs should be successfully inserted into the table + # + # @test_assumptions A Cassandra cluster with version 2.1.0 or higher. + # @test_category data_types:udt + # + def test_can_insert_quoted_udts + skip('UDTs are only available in C* after 2.1') if CCM.cassandra_version < '2.1.0' cluster = Cassandra.cluster session = cluster.connect("simplex") session.execute('CREATE TYPE "User" (age int, name text, gender text)') - session.execute('CREATE TABLE mytable (a int PRIMARY KEY, b frozen<"User">)') + session.execute('CREATE TABLE "MyTable" (a int PRIMARY KEY, b frozen<"User">)') - # RUBY-291 - We can't connect to the cluster when a UDT name contains capital letters and is thus quoted. + # RUBY-291 - We can't connect to the cluster when a UDT name contains capital letters (and is thus quoted) cluster2 = Cassandra.cluster session2 = cluster2.connect("simplex") # Test non-prepared statement - session2.execute("INSERT INTO mytable (a, b) VALUES (0, {age: 30, name: 'John', gender: 'male'})") - user_value = session2.execute("SELECT b FROM mytable where a=0").first['b'] + session2.execute("INSERT INTO \"MyTable\" (a, b) VALUES (0, {age: 30, name: 'John', gender: 'male'})") + user_value = session2.execute('SELECT b FROM "MyTable" where a=0').first['b'] assert_equal 30, user_value.age assert_equal 'John', user_value.name assert_equal 'male', user_value.gender # Test prepared statement - insert = Retry.with_attempts(5) { session2.prepare("INSERT INTO simplex.mytable (a, b) VALUES (?, ?)") } + insert = Retry.with_attempts(5) { session2.prepare("INSERT INTO \"MyTable\" (a, b) VALUES (?, ?)") } Retry.with_attempts(5) { session2.execute(insert, arguments: [1, Cassandra::UDT.new(age: 25, name: 'Jane', gender: 'female')]) } - user_value = session2.execute("SELECT b FROM mytable where a=1").first['b'] + user_value = session2.execute('SELECT b FROM "MyTable" where a=1').first['b'] assert_equal 25, user_value.age assert_equal 'Jane', user_value.name assert_equal 'female', user_value.gender From 7f04b30d3c0609980fcdfab2a79dd9b2622b86b0 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Thu, 11 May 2017 14:58:05 -0700 Subject: [PATCH 188/196] Update README and CHANGELOG in preparation for 3.2.0 release. --- CHANGELOG.md | 7 ++++++- README.md | 45 +++++++++++++++++++++------------------------ 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b8f38612..3c49a9aa9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,14 @@ -# 3.1.1 +# 3.2.0 Features: +* [RUBY-294](https://datastax-oss.atlassian.net/browse/RUBY-291) Support MRI 2.4.x. Thanks, @lautis, for this contribution! Bug Fixes: * [RUBY-291](https://datastax-oss.atlassian.net/browse/RUBY-291) Driver fails to connect to cluster when a table column type has a quoted name. +* [RUBY-292](https://datastax-oss.atlassian.net/browse/RUBY-291) Driver sporadically crashes with "undefined method 'ip'" error. +Thanks, @grosser, for the fix! +* [RUBY-295](https://datastax-oss.atlassian.net/browse/RUBY-295) When a custom address resolver is configured, +consult it when handling all host events, and thus prevent the creation of invalid Host objects. # 3.1.0 Features: diff --git a/README.md b/README.md index 064988ff6..f1ac9e721 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,14 @@ version [here](http://docs.datastax.com/en/developer/ruby-driver/latest).* A Ruby client driver for Apache Cassandra. This driver works exclusively with the Cassandra Query Language version 3 (CQL3) and Cassandra's native protocol. +Use the [Ruby DSE driver](https://github.com/datastax/ruby-dse-driver.git) for +better compatibility and support for DataStax Enterprise. + - Code: https://github.com/datastax/ruby-driver - Docs: http://docs.datastax.com/en/developer/ruby-driver - Jira: https://datastax-oss.atlassian.net/browse/RUBY - Mailing List: https://groups.google.com/a/lists.datastax.com/forum/#!forum/ruby-driver-user -- IRC: #datastax-drivers on [irc.freenode.net](http://freenode.net>) +- Slack: `#datastax-drivers` channel at https://academy.datastax.com/slack - Twitter: Follow the latest news about DataStax Drivers - [@stamhankar999](http://twitter.com/stamhankar999), [@avalanche123](http://twitter.com/avalanche123), [@al3xandru](https://twitter.com/al3xandru) 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: @@ -33,14 +36,13 @@ This driver is based on [the cql-rb gem](https://github.com/iconara/cql-rb) by [ This driver works exclusively with the Cassandra Query Language v3 (CQL3) and Cassandra's native protocol. The current version works with: -* Apache Cassandra versions 1.2, 2.0, 2.1, 2.2, and 3.x -* DataStax Enterprise 4.0 and above. -* Ruby (MRI) 2.2, 2.3 -* JRuby 1.7 +* Apache Cassandra versions 2.1, 2.2, and 3.x +* DataStax Enterprise 4.8 and above. However, the [Ruby DSE driver](https://github.com/datastax/ruby-dse-driver.git) provides more features and is recommended for use with DataStax Enterprise. +* Ruby (MRI) 2.2, 2.3, 2.4 +* JRuby 1.7, 9k -__Note__: JRuby 1.6 is not officially supported, although 1.6.8 should work. Rubinius is not supported. -MRI 2.0, 2.1, and JRuby 9k are not officially supported, but they should work. 1.9.3 is deprecated -and is likely to break in the release following 3.0. +__Note__: Rubinius is not supported. MRI 2.0, and 2.1 are not officially supported, but they should work. MRI 1.9.3 is deprecated +and may break in any release after 3.0. ## Feedback Requested @@ -101,6 +103,15 @@ In the examples directory, you can find [an example of how to wrap the ruby driv interface parity with cql-rb](https://github.com/datastax/ruby-driver/blob/master/examples/cql-rb-wrapper.rb) to assist you with gradual upgrade. +If you are upgrading to DataStax Enterprise, use the [Ruby DSE driver](https://github.com/datastax/ruby-dse-driver.git) +for more features and better compatibility. + +## What's new in v3.2 +This minor release adds support for MRI 2.4.x and also contains a few miscellaneous defect fixes. + +See the [changelog](https://github.com/datastax/ruby-driver/blob/master/CHANGELOG.md) for more information on all +changes in this version and past versions. + ## What's new in v3.1 This minor release introduces features and fixes around resiliency, schema metadata, usability, and performance. One @@ -110,9 +121,6 @@ Execution profiles allow you to group various execution options into a 'profile' profile at execution time. Get the scoop [here](http://docs.datastax.com/en/developer/ruby-driver/3.1/features/basics/execution_profiles). -See the [changelog](https://github.com/datastax/ruby-driver/blob/master/CHANGELOG.md) for more information on all -changes in this version and past versions. - ## What's new in v3.0 ### Features: @@ -159,16 +167,6 @@ batch.add(query, arguments: {p1: 'val1'}) Specify a positive value (or nil for unlimited) for `max_remote_hosts_to_use` when initializing the policy to allow remote node use. * Unspecified variables in statements previously resulted in an exception. Now they are essentially ignored or treated as null. -### Bug Fixes: - -* [[RUBY-120](https://datastax-oss.atlassian.net/browse/RUBY-120)] Tuples and UDTs can be used in sets and hash keys. -* [[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. -* [[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. -* [[RUBY-151](https://datastax-oss.atlassian.net/browse/RUBY-151)] Decode incomplete UDTs properly. -* [[RUBY-155](https://datastax-oss.atlassian.net/browse/RUBY-155)] Request timeout timer should not include request queuing time. -* [[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. -* [[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. - ## Code examples The DataStax Ruby Driver uses the awesome [Cucumber Framework](http://cukes.info/) for @@ -197,7 +195,7 @@ bundle install --without docs CASSANDRA_VERSION=3.1.1 bundle exec cucumber # runs end-to-end tests (or bundle exec rake cucumber) CASSANDRA_VERSION=3.0.0 bundle exec rspec # runs unit tests (or bundle exec rake rspec) CASSANDRA_VERSION=2.1.12 bundle exec rake integration # run integration tests -CASSANDRA_VERSION=2.0.17 bundle exec rake test # run both as well as integration tests +CASSANDRA_VERSION=2.1.12 bundle exec rake test # run both as well as integration tests ``` ## Changelog & versioning @@ -223,7 +221,6 @@ the release. * Specifying a `protocol_version` option of 1 or 2 in cluster options will fail with a `NoHostsAvailable` error rather than a `ProtocolError` against Cassandra node versions 3.0-3.4. -* JRuby 1.6 is not officially supported, although 1.6.8 should work. * Because the driver reactor is using `IO.select`, the maximum number of tcp connections allowed is 1024. * Because the driver uses `IO#write_nonblock`, Windows is not supported. @@ -245,7 +242,7 @@ Driver for Apache Cassandra will continue on this project, while ## Copyright -Copyright 2013-2016 DataStax, Inc. +Copyright 2013-2017 DataStax, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at From a2423415d331b013e7473005d591f139913075df Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Thu, 11 May 2017 18:17:47 -0700 Subject: [PATCH 189/196] Update Copyright notices on all files. --- LICENSE | 2 +- benchmarking/async-prepared-insert-cql-rb.rb | 2 +- benchmarking/async-prepared-insert-ruby-driver.rb | 2 +- benchmarking/async-prepared-select-cql-rb.rb | 2 +- benchmarking/async-prepared-select-ruby-driver.rb | 2 +- benchmarking/async-unprepared-insert-cql-rb.rb | 2 +- benchmarking/async-unprepared-insert-ruby-driver.rb | 2 +- benchmarking/async-unprepared-select-cql-rb.rb | 2 +- benchmarking/async-unprepared-select-ruby-driver.rb | 2 +- benchmarking/benchmark.rb | 2 +- benchmarking/sync-prepared-insert-cql-rb.rb | 2 +- benchmarking/sync-prepared-insert-ruby-driver.rb | 2 +- benchmarking/sync-prepared-select-cql-rb.rb | 2 +- benchmarking/sync-prepared-select-ruby-driver.rb | 2 +- benchmarking/sync-unprepared-insert-cql-rb.rb | 2 +- benchmarking/sync-unprepared-insert-ruby-driver.rb | 2 +- benchmarking/sync-unprepared-select-cql-rb.rb | 2 +- benchmarking/sync-unprepared-select-ruby-driver.rb | 2 +- ext/cassandra_murmur3/CassandraMurmur3Service.java | 2 +- ext/cassandra_murmur3/cassandra_murmur3.c | 2 +- integration/client_error_test.rb | 2 +- integration/client_warnings_test.rb | 4 ++-- integration/control_connection_test.rb | 2 +- integration/custom_payload_test.rb | 2 +- integration/datatype_utils.rb | 2 +- integration/functions/user_defined_aggregate_test.rb | 2 +- integration/functions/user_defined_function_test.rb | 2 +- integration/idempotency_test.rb | 2 +- integration/indexes/indexes_test.rb | 2 +- integration/indexes/materialized_view_test.rb | 2 +- integration/integration_test_case.rb | 2 +- integration/load_balancing/round_robin_test.rb | 2 +- integration/load_balancing/token_aware_test.rb | 2 +- integration/load_balancing/try_next_host_retry_policy.rb | 2 +- integration/metadata_test.rb | 2 +- integration/security/authentication_test.rb | 2 +- integration/security/ssl_authenticated_encryption_test.rb | 2 +- integration/security/ssl_encryption_test.rb | 2 +- integration/serial_consistency_test.rb | 2 +- integration/session_test.rb | 2 +- integration/stress_tests/cluster_stress_test.rb | 2 +- integration/stress_tests/stress_helper.rb | 2 +- integration/types/datatype_test.rb | 2 +- integration/types/user_defined_type_test.rb | 2 +- lib/cassandra.rb | 2 +- lib/cassandra/address_resolution.rb | 2 +- lib/cassandra/address_resolution/policies/ec2_multi_region.rb | 2 +- lib/cassandra/address_resolution/policies/none.rb | 2 +- lib/cassandra/aggregate.rb | 2 +- lib/cassandra/argument.rb | 2 +- lib/cassandra/attr_boolean.rb | 2 +- lib/cassandra/auth.rb | 2 +- lib/cassandra/auth/providers.rb | 2 +- lib/cassandra/auth/providers/password.rb | 2 +- lib/cassandra/cassandra_logger.rb | 2 +- lib/cassandra/cluster.rb | 2 +- lib/cassandra/cluster/client.rb | 2 +- lib/cassandra/cluster/connection_pool.rb | 2 +- lib/cassandra/cluster/connector.rb | 2 +- lib/cassandra/cluster/control_connection.rb | 2 +- lib/cassandra/cluster/failed_connection.rb | 2 +- lib/cassandra/cluster/metadata.rb | 2 +- lib/cassandra/cluster/options.rb | 2 +- lib/cassandra/cluster/registry.rb | 2 +- lib/cassandra/cluster/schema.rb | 2 +- lib/cassandra/cluster/schema/cql_type_parser.rb | 2 +- lib/cassandra/cluster/schema/fetchers.rb | 2 +- lib/cassandra/cluster/schema/fqcn_type_parser.rb | 2 +- lib/cassandra/cluster/schema/partitioners.rb | 2 +- lib/cassandra/cluster/schema/partitioners/murmur3.rb | 2 +- lib/cassandra/cluster/schema/partitioners/ordered.rb | 2 +- lib/cassandra/cluster/schema/partitioners/random.rb | 2 +- lib/cassandra/cluster/schema/replication_strategies.rb | 2 +- .../cluster/schema/replication_strategies/network_topology.rb | 2 +- lib/cassandra/cluster/schema/replication_strategies/none.rb | 2 +- lib/cassandra/cluster/schema/replication_strategies/simple.rb | 2 +- lib/cassandra/column.rb | 2 +- lib/cassandra/column_container.rb | 2 +- lib/cassandra/compression.rb | 2 +- lib/cassandra/compression/compressors/lz4.rb | 2 +- lib/cassandra/compression/compressors/snappy.rb | 2 +- lib/cassandra/custom_data.rb | 2 +- lib/cassandra/driver.rb | 2 +- lib/cassandra/errors.rb | 2 +- lib/cassandra/execution/info.rb | 2 +- lib/cassandra/execution/options.rb | 2 +- lib/cassandra/execution/profile.rb | 2 +- lib/cassandra/execution/profile_manager.rb | 2 +- lib/cassandra/execution/trace.rb | 2 +- lib/cassandra/executors.rb | 2 +- lib/cassandra/function.rb | 2 +- lib/cassandra/function_collection.rb | 2 +- lib/cassandra/future.rb | 2 +- lib/cassandra/host.rb | 2 +- lib/cassandra/index.rb | 2 +- lib/cassandra/keyspace.rb | 2 +- lib/cassandra/listener.rb | 2 +- lib/cassandra/load_balancing.rb | 2 +- lib/cassandra/load_balancing/policies.rb | 2 +- lib/cassandra/load_balancing/policies/dc_aware_round_robin.rb | 2 +- lib/cassandra/load_balancing/policies/round_robin.rb | 2 +- lib/cassandra/load_balancing/policies/token_aware.rb | 2 +- lib/cassandra/load_balancing/policies/white_list.rb | 2 +- lib/cassandra/materialized_view.rb | 2 +- lib/cassandra/null_logger.rb | 2 +- lib/cassandra/protocol.rb | 2 +- lib/cassandra/protocol/coder.rb | 2 +- lib/cassandra/protocol/cql_byte_buffer.rb | 2 +- lib/cassandra/protocol/cql_protocol_handler.rb | 2 +- lib/cassandra/protocol/request.rb | 2 +- lib/cassandra/protocol/requests/auth_response_request.rb | 2 +- lib/cassandra/protocol/requests/batch_request.rb | 2 +- lib/cassandra/protocol/requests/credentials_request.rb | 2 +- lib/cassandra/protocol/requests/execute_request.rb | 2 +- lib/cassandra/protocol/requests/options_request.rb | 2 +- lib/cassandra/protocol/requests/prepare_request.rb | 2 +- lib/cassandra/protocol/requests/query_request.rb | 2 +- lib/cassandra/protocol/requests/register_request.rb | 2 +- lib/cassandra/protocol/requests/startup_request.rb | 2 +- lib/cassandra/protocol/requests/void_query_request.rb | 2 +- lib/cassandra/protocol/response.rb | 2 +- .../protocol/responses/already_exists_error_response.rb | 2 +- lib/cassandra/protocol/responses/auth_challenge_response.rb | 2 +- lib/cassandra/protocol/responses/auth_success_response.rb | 2 +- lib/cassandra/protocol/responses/authenticate_response.rb | 2 +- lib/cassandra/protocol/responses/error_response.rb | 2 +- lib/cassandra/protocol/responses/event_response.rb | 2 +- .../protocol/responses/function_failure_error_response.rb | 2 +- lib/cassandra/protocol/responses/prepared_result_response.rb | 2 +- lib/cassandra/protocol/responses/raw_rows_result_response.rb | 2 +- .../protocol/responses/read_failure_error_response.rb | 2 +- .../protocol/responses/read_timeout_error_response.rb | 2 +- lib/cassandra/protocol/responses/ready_response.rb | 2 +- lib/cassandra/protocol/responses/result_response.rb | 2 +- lib/cassandra/protocol/responses/rows_result_response.rb | 2 +- .../protocol/responses/schema_change_event_response.rb | 2 +- .../protocol/responses/schema_change_result_response.rb | 2 +- .../protocol/responses/set_keyspace_result_response.rb | 2 +- .../protocol/responses/status_change_event_response.rb | 2 +- lib/cassandra/protocol/responses/supported_response.rb | 2 +- .../protocol/responses/topology_change_event_response.rb | 2 +- .../protocol/responses/unavailable_error_response.rb | 2 +- lib/cassandra/protocol/responses/unprepared_error_response.rb | 2 +- lib/cassandra/protocol/responses/void_result_response.rb | 2 +- .../protocol/responses/write_failure_error_response.rb | 2 +- .../protocol/responses/write_timeout_error_response.rb | 2 +- lib/cassandra/protocol/v1.rb | 2 +- lib/cassandra/protocol/v3.rb | 2 +- lib/cassandra/protocol/v4.rb | 2 +- lib/cassandra/reconnection.rb | 2 +- lib/cassandra/reconnection/policies.rb | 2 +- lib/cassandra/reconnection/policies/constant.rb | 2 +- lib/cassandra/reconnection/policies/exponential.rb | 2 +- lib/cassandra/result.rb | 2 +- lib/cassandra/retry.rb | 2 +- lib/cassandra/retry/policies.rb | 2 +- lib/cassandra/retry/policies/default.rb | 2 +- lib/cassandra/retry/policies/downgrading_consistency.rb | 2 +- lib/cassandra/retry/policies/fallthrough.rb | 2 +- lib/cassandra/session.rb | 2 +- lib/cassandra/statement.rb | 2 +- lib/cassandra/statements.rb | 2 +- lib/cassandra/statements/batch.rb | 2 +- lib/cassandra/statements/bound.rb | 2 +- lib/cassandra/statements/prepared.rb | 2 +- lib/cassandra/statements/simple.rb | 2 +- lib/cassandra/statements/void.rb | 2 +- lib/cassandra/table.rb | 2 +- lib/cassandra/time.rb | 2 +- lib/cassandra/time_uuid.rb | 2 +- lib/cassandra/timestamp_generator.rb | 2 +- lib/cassandra/timestamp_generator/simple.rb | 2 +- lib/cassandra/timestamp_generator/ticking_on_duplicate.rb | 2 +- lib/cassandra/trigger.rb | 2 +- lib/cassandra/tuple.rb | 2 +- lib/cassandra/types.rb | 2 +- lib/cassandra/udt.rb | 2 +- lib/cassandra/util.rb | 2 +- lib/cassandra/uuid.rb | 2 +- lib/cassandra/uuid/generator.rb | 2 +- lib/cassandra/version.rb | 2 +- lib/datastax/cassandra.rb | 2 +- .../address_resolution/policies/ec2_multi_region_spec.rb | 2 +- spec/cassandra/auth/providers/password_spec.rb | 2 +- spec/cassandra/cluster/client_spec.rb | 2 +- spec/cassandra/cluster/connection_pool_spec.rb | 2 +- spec/cassandra/cluster/control_connection_spec.rb | 2 +- spec/cassandra/cluster/metadata_spec.rb | 2 +- spec/cassandra/cluster/options_spec.rb | 2 +- spec/cassandra/cluster/registry_spec.rb | 2 +- spec/cassandra/cluster/schema/fetchers_spec.rb | 2 +- spec/cassandra/cluster/schema/fqcn_type_parser_spec.rb | 2 +- spec/cassandra/cluster/schema/partitioners/murmur3_spec.rb | 2 +- spec/cassandra/cluster/schema/partitioners/random_spec.rb | 2 +- .../schema/replication_strategies/network_topology_spec.rb | 2 +- .../cluster/schema/replication_strategies/none_spec.rb | 2 +- spec/cassandra/cluster/schema_spec.rb | 2 +- spec/cassandra/cluster_spec.rb | 2 +- spec/cassandra/compression/common.rb | 2 +- spec/cassandra/compression/compressors/lz4_spec.rb | 2 +- spec/cassandra/compression/compressors/snappy_spec.rb | 2 +- spec/cassandra/execution/options_spec.rb | 2 +- spec/cassandra/execution/profile_manager_spec.rb | 2 +- spec/cassandra/execution/profile_spec.rb | 2 +- spec/cassandra/execution/trace_spec.rb | 2 +- spec/cassandra/executors/same_thread_spec.rb | 2 +- spec/cassandra/executors/thread_pool_spec.rb | 2 +- spec/cassandra/future_spec.rb | 2 +- spec/cassandra/index_spec.rb | 2 +- spec/cassandra/keyspace_spec.rb | 2 +- .../load_balancing/policies/dc_aware_round_robin_spec.rb | 2 +- spec/cassandra/load_balancing/policies/round_robin_spec.rb | 2 +- spec/cassandra/load_balancing/policies/token_aware_spec.rb | 2 +- spec/cassandra/load_balancing/policies/white_list_spec.rb | 2 +- spec/cassandra/materialized_view_spec.rb | 2 +- spec/cassandra/murmur3_spec.rb | 2 +- spec/cassandra/protocol/coder_spec.rb | 2 +- spec/cassandra/protocol/cql_byte_buffer_spec.rb | 2 +- spec/cassandra/protocol/cql_protocol_handler_spec.rb | 2 +- .../cassandra/protocol/requests/auth_response_request_spec.rb | 2 +- spec/cassandra/protocol/requests/batch_request_spec.rb | 2 +- spec/cassandra/protocol/requests/credentials_request_spec.rb | 2 +- spec/cassandra/protocol/requests/execute_request_spec.rb | 2 +- spec/cassandra/protocol/requests/options_request_spec.rb | 2 +- spec/cassandra/protocol/requests/prepare_request_spec.rb | 2 +- spec/cassandra/protocol/requests/query_request_spec.rb | 2 +- spec/cassandra/protocol/requests/register_request_spec.rb | 2 +- spec/cassandra/protocol/requests/startup_request_spec.rb | 2 +- .../protocol/responses/auth_challenge_response_spec.rb | 2 +- .../protocol/responses/auth_success_response_spec.rb | 2 +- .../protocol/responses/authenticate_response_spec.rb | 2 +- spec/cassandra/protocol/responses/error_response_spec.rb | 2 +- .../protocol/responses/prepared_result_response_spec.rb | 2 +- .../protocol/responses/raw_rows_result_response_spec.rb | 2 +- spec/cassandra/protocol/responses/ready_response_spec.rb | 2 +- .../cassandra/protocol/responses/rows_result_response_spec.rb | 2 +- .../protocol/responses/schema_change_event_response_spec.rb | 2 +- .../protocol/responses/schema_change_result_response_spec.rb | 2 +- .../protocol/responses/set_keyspace_result_response_spec.rb | 2 +- .../protocol/responses/status_change_event_response_spec.rb | 2 +- spec/cassandra/protocol/responses/supported_response_spec.rb | 2 +- .../protocol/responses/topology_change_event_response_spec.rb | 2 +- .../cassandra/protocol/responses/void_result_response_spec.rb | 2 +- spec/cassandra/protocol/v1/decoder_spec.rb | 2 +- spec/cassandra/protocol/v3/decoder_spec.rb | 2 +- spec/cassandra/protocol/v4/decoder_spec.rb | 2 +- spec/cassandra/retry/policies/default_spec.rb | 2 +- spec/cassandra/retry/policies/downgrading_consistency_spec.rb | 2 +- spec/cassandra/retry/policies/fallthrough_spec.rb | 2 +- spec/cassandra/session_spec.rb | 2 +- spec/cassandra/table_spec.rb | 2 +- spec/cassandra/time_uuid_spec.rb | 2 +- spec/cassandra/util_spec.rb | 2 +- spec/cassandra/uuid/generator_spec.rb | 2 +- spec/cassandra/uuid_spec.rb | 2 +- spec/regressions/RUBY-189_spec.rb | 2 +- spec/spec_helper.rb | 2 +- spec/support/await_helper.rb | 2 +- spec/support/bytes_helper.rb | 2 +- spec/support/fake_cluster_registry.rb | 2 +- spec/support/fake_io_reactor.rb | 2 +- spec/support/stub_io_reactor.rb | 2 +- support/ccm.rb | 2 +- support/triggers/AuditTrigger.properties | 2 +- 264 files changed, 265 insertions(+), 265 deletions(-) diff --git a/LICENSE b/LICENSE index c0868f0d3..6132ffb09 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright 2013-2016 DataStax, Inc. +Copyright 2013-2017 DataStax, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/benchmarking/async-prepared-insert-cql-rb.rb b/benchmarking/async-prepared-insert-cql-rb.rb index d6344c20f..9c2f1f014 100644 --- a/benchmarking/async-prepared-insert-cql-rb.rb +++ b/benchmarking/async-prepared-insert-cql-rb.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/benchmarking/async-prepared-insert-ruby-driver.rb b/benchmarking/async-prepared-insert-ruby-driver.rb index d874dadd1..90d73eea3 100644 --- a/benchmarking/async-prepared-insert-ruby-driver.rb +++ b/benchmarking/async-prepared-insert-ruby-driver.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/benchmarking/async-prepared-select-cql-rb.rb b/benchmarking/async-prepared-select-cql-rb.rb index 815dfbfa8..01ce04f11 100644 --- a/benchmarking/async-prepared-select-cql-rb.rb +++ b/benchmarking/async-prepared-select-cql-rb.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/benchmarking/async-prepared-select-ruby-driver.rb b/benchmarking/async-prepared-select-ruby-driver.rb index 7211f5ba9..4d267115b 100644 --- a/benchmarking/async-prepared-select-ruby-driver.rb +++ b/benchmarking/async-prepared-select-ruby-driver.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/benchmarking/async-unprepared-insert-cql-rb.rb b/benchmarking/async-unprepared-insert-cql-rb.rb index a0dcf20f2..fa5aea5a5 100644 --- a/benchmarking/async-unprepared-insert-cql-rb.rb +++ b/benchmarking/async-unprepared-insert-cql-rb.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/benchmarking/async-unprepared-insert-ruby-driver.rb b/benchmarking/async-unprepared-insert-ruby-driver.rb index 1410920e1..420505bf3 100644 --- a/benchmarking/async-unprepared-insert-ruby-driver.rb +++ b/benchmarking/async-unprepared-insert-ruby-driver.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/benchmarking/async-unprepared-select-cql-rb.rb b/benchmarking/async-unprepared-select-cql-rb.rb index 0b53b5f82..fb55c48fc 100644 --- a/benchmarking/async-unprepared-select-cql-rb.rb +++ b/benchmarking/async-unprepared-select-cql-rb.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/benchmarking/async-unprepared-select-ruby-driver.rb b/benchmarking/async-unprepared-select-ruby-driver.rb index b8d66a183..3eb3ebb3f 100644 --- a/benchmarking/async-unprepared-select-ruby-driver.rb +++ b/benchmarking/async-unprepared-select-ruby-driver.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/benchmarking/benchmark.rb b/benchmarking/benchmark.rb index 0199f06b2..b0dba8b4f 100644 --- a/benchmarking/benchmark.rb +++ b/benchmarking/benchmark.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/benchmarking/sync-prepared-insert-cql-rb.rb b/benchmarking/sync-prepared-insert-cql-rb.rb index 6fe78f4d2..fd8f4ba3a 100644 --- a/benchmarking/sync-prepared-insert-cql-rb.rb +++ b/benchmarking/sync-prepared-insert-cql-rb.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/benchmarking/sync-prepared-insert-ruby-driver.rb b/benchmarking/sync-prepared-insert-ruby-driver.rb index f8be56018..23fecd7d3 100644 --- a/benchmarking/sync-prepared-insert-ruby-driver.rb +++ b/benchmarking/sync-prepared-insert-ruby-driver.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/benchmarking/sync-prepared-select-cql-rb.rb b/benchmarking/sync-prepared-select-cql-rb.rb index cbeecc590..d342d0101 100644 --- a/benchmarking/sync-prepared-select-cql-rb.rb +++ b/benchmarking/sync-prepared-select-cql-rb.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/benchmarking/sync-prepared-select-ruby-driver.rb b/benchmarking/sync-prepared-select-ruby-driver.rb index f0e85f7a5..398b7e8cf 100644 --- a/benchmarking/sync-prepared-select-ruby-driver.rb +++ b/benchmarking/sync-prepared-select-ruby-driver.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/benchmarking/sync-unprepared-insert-cql-rb.rb b/benchmarking/sync-unprepared-insert-cql-rb.rb index 7daf80360..610637b5f 100644 --- a/benchmarking/sync-unprepared-insert-cql-rb.rb +++ b/benchmarking/sync-unprepared-insert-cql-rb.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/benchmarking/sync-unprepared-insert-ruby-driver.rb b/benchmarking/sync-unprepared-insert-ruby-driver.rb index b78370efb..9035f6ec3 100644 --- a/benchmarking/sync-unprepared-insert-ruby-driver.rb +++ b/benchmarking/sync-unprepared-insert-ruby-driver.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/benchmarking/sync-unprepared-select-cql-rb.rb b/benchmarking/sync-unprepared-select-cql-rb.rb index 7228d87ee..9255d899d 100644 --- a/benchmarking/sync-unprepared-select-cql-rb.rb +++ b/benchmarking/sync-unprepared-select-cql-rb.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/benchmarking/sync-unprepared-select-ruby-driver.rb b/benchmarking/sync-unprepared-select-ruby-driver.rb index 8145318a0..5908da7bd 100644 --- a/benchmarking/sync-unprepared-select-ruby-driver.rb +++ b/benchmarking/sync-unprepared-select-ruby-driver.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/ext/cassandra_murmur3/CassandraMurmur3Service.java b/ext/cassandra_murmur3/CassandraMurmur3Service.java index 8ce598703..aa3576e48 100644 --- a/ext/cassandra_murmur3/CassandraMurmur3Service.java +++ b/ext/cassandra_murmur3/CassandraMurmur3Service.java @@ -1,4 +1,4 @@ -// Copyright 2013-2016 DataStax, Inc. +// Copyright 2013-2017 DataStax, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/ext/cassandra_murmur3/cassandra_murmur3.c b/ext/cassandra_murmur3/cassandra_murmur3.c index 26241e4a2..04ecabda1 100644 --- a/ext/cassandra_murmur3/cassandra_murmur3.c +++ b/ext/cassandra_murmur3/cassandra_murmur3.c @@ -1,4 +1,4 @@ -// Copyright 2013-2016 DataStax, Inc. +// Copyright 2013-2017 DataStax, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/integration/client_error_test.rb b/integration/client_error_test.rb index 31c2292a4..e0cbda7ec 100644 --- a/integration/client_error_test.rb +++ b/integration/client_error_test.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/integration/client_warnings_test.rb b/integration/client_warnings_test.rb index d1686b47b..7b4038f6d 100644 --- a/integration/client_warnings_test.rb +++ b/integration/client_warnings_test.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -170,4 +170,4 @@ def test_batch_exceeding_length_with_trace_and_custom_payload assert_match @exceeding_warning, execution_info.warnings.first assert_equal custom_payload, execution_info.payload end -end \ No newline at end of file +end diff --git a/integration/control_connection_test.rb b/integration/control_connection_test.rb index b41fc7f6a..ca1f1a681 100644 --- a/integration/control_connection_test.rb +++ b/integration/control_connection_test.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/integration/custom_payload_test.rb b/integration/custom_payload_test.rb index bb88c26aa..4ffa673fa 100644 --- a/integration/custom_payload_test.rb +++ b/integration/custom_payload_test.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/integration/datatype_utils.rb b/integration/datatype_utils.rb index 9de040591..01ba254b4 100644 --- a/integration/datatype_utils.rb +++ b/integration/datatype_utils.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/integration/functions/user_defined_aggregate_test.rb b/integration/functions/user_defined_aggregate_test.rb index 88a75b0e4..26bde34e5 100644 --- a/integration/functions/user_defined_aggregate_test.rb +++ b/integration/functions/user_defined_aggregate_test.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/integration/functions/user_defined_function_test.rb b/integration/functions/user_defined_function_test.rb index 6010d7a2b..9a1ab08dc 100644 --- a/integration/functions/user_defined_function_test.rb +++ b/integration/functions/user_defined_function_test.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/integration/idempotency_test.rb b/integration/idempotency_test.rb index 13aabd18c..d3500d4ce 100644 --- a/integration/idempotency_test.rb +++ b/integration/idempotency_test.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/integration/indexes/indexes_test.rb b/integration/indexes/indexes_test.rb index fc2e46c08..8d09627fa 100644 --- a/integration/indexes/indexes_test.rb +++ b/integration/indexes/indexes_test.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/integration/indexes/materialized_view_test.rb b/integration/indexes/materialized_view_test.rb index d30b36121..c8f316cb6 100644 --- a/integration/indexes/materialized_view_test.rb +++ b/integration/indexes/materialized_view_test.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/integration/integration_test_case.rb b/integration/integration_test_case.rb index bd3959539..fe5aa4e1a 100644 --- a/integration/integration_test_case.rb +++ b/integration/integration_test_case.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/integration/load_balancing/round_robin_test.rb b/integration/load_balancing/round_robin_test.rb index cdb23ca78..9da97ea7a 100644 --- a/integration/load_balancing/round_robin_test.rb +++ b/integration/load_balancing/round_robin_test.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/integration/load_balancing/token_aware_test.rb b/integration/load_balancing/token_aware_test.rb index c04c7f691..e6e6a4443 100644 --- a/integration/load_balancing/token_aware_test.rb +++ b/integration/load_balancing/token_aware_test.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/integration/load_balancing/try_next_host_retry_policy.rb b/integration/load_balancing/try_next_host_retry_policy.rb index 597972ecb..8abe9f4c4 100644 --- a/integration/load_balancing/try_next_host_retry_policy.rb +++ b/integration/load_balancing/try_next_host_retry_policy.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/integration/metadata_test.rb b/integration/metadata_test.rb index b0235d12c..19b855cb9 100644 --- a/integration/metadata_test.rb +++ b/integration/metadata_test.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/integration/security/authentication_test.rb b/integration/security/authentication_test.rb index 0f46469d3..9b335281b 100644 --- a/integration/security/authentication_test.rb +++ b/integration/security/authentication_test.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/integration/security/ssl_authenticated_encryption_test.rb b/integration/security/ssl_authenticated_encryption_test.rb index c425b2ce6..61185d75e 100644 --- a/integration/security/ssl_authenticated_encryption_test.rb +++ b/integration/security/ssl_authenticated_encryption_test.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/integration/security/ssl_encryption_test.rb b/integration/security/ssl_encryption_test.rb index c18b98072..daea13ed8 100644 --- a/integration/security/ssl_encryption_test.rb +++ b/integration/security/ssl_encryption_test.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/integration/serial_consistency_test.rb b/integration/serial_consistency_test.rb index 4366f6db3..0bc7c6ecf 100644 --- a/integration/serial_consistency_test.rb +++ b/integration/serial_consistency_test.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/integration/session_test.rb b/integration/session_test.rb index 5039c1cac..6892148b2 100644 --- a/integration/session_test.rb +++ b/integration/session_test.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/integration/stress_tests/cluster_stress_test.rb b/integration/stress_tests/cluster_stress_test.rb index c0a8fe0bd..27094c077 100644 --- a/integration/stress_tests/cluster_stress_test.rb +++ b/integration/stress_tests/cluster_stress_test.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/integration/stress_tests/stress_helper.rb b/integration/stress_tests/stress_helper.rb index fbe2e9c16..a4c034ef5 100644 --- a/integration/stress_tests/stress_helper.rb +++ b/integration/stress_tests/stress_helper.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/integration/types/datatype_test.rb b/integration/types/datatype_test.rb index bde72c1ef..59648ced7 100644 --- a/integration/types/datatype_test.rb +++ b/integration/types/datatype_test.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/integration/types/user_defined_type_test.rb b/integration/types/user_defined_type_test.rb index 49a6c0bde..b5a0490be 100644 --- a/integration/types/user_defined_type_test.rb +++ b/integration/types/user_defined_type_test.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra.rb b/lib/cassandra.rb index b96cced7e..f85c76e83 100644 --- a/lib/cassandra.rb +++ b/lib/cassandra.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/address_resolution.rb b/lib/cassandra/address_resolution.rb index be3040a18..91a9d4f4a 100644 --- a/lib/cassandra/address_resolution.rb +++ b/lib/cassandra/address_resolution.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/address_resolution/policies/ec2_multi_region.rb b/lib/cassandra/address_resolution/policies/ec2_multi_region.rb index b3dab056f..a5eb0028c 100644 --- a/lib/cassandra/address_resolution/policies/ec2_multi_region.rb +++ b/lib/cassandra/address_resolution/policies/ec2_multi_region.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/address_resolution/policies/none.rb b/lib/cassandra/address_resolution/policies/none.rb index f6b8802c4..daca2bd03 100644 --- a/lib/cassandra/address_resolution/policies/none.rb +++ b/lib/cassandra/address_resolution/policies/none.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/aggregate.rb b/lib/cassandra/aggregate.rb index f607e1060..8491f7fcb 100644 --- a/lib/cassandra/aggregate.rb +++ b/lib/cassandra/aggregate.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/argument.rb b/lib/cassandra/argument.rb index fbc858f6b..5cbfd54ef 100644 --- a/lib/cassandra/argument.rb +++ b/lib/cassandra/argument.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/attr_boolean.rb b/lib/cassandra/attr_boolean.rb index 7fbb2668e..29ce86bf6 100644 --- a/lib/cassandra/attr_boolean.rb +++ b/lib/cassandra/attr_boolean.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/auth.rb b/lib/cassandra/auth.rb index e029beaca..db995e58e 100644 --- a/lib/cassandra/auth.rb +++ b/lib/cassandra/auth.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/auth/providers.rb b/lib/cassandra/auth/providers.rb index 75cdcecff..9f12c8c0c 100644 --- a/lib/cassandra/auth/providers.rb +++ b/lib/cassandra/auth/providers.rb @@ -1,5 +1,5 @@ #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/auth/providers/password.rb b/lib/cassandra/auth/providers/password.rb index d1f6030a5..0d3e6a43f 100644 --- a/lib/cassandra/auth/providers/password.rb +++ b/lib/cassandra/auth/providers/password.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/cassandra_logger.rb b/lib/cassandra/cassandra_logger.rb index c653493ce..1c9a0cc1a 100644 --- a/lib/cassandra/cassandra_logger.rb +++ b/lib/cassandra/cassandra_logger.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/cluster.rb b/lib/cassandra/cluster.rb index c09eca0bc..923f0174b 100644 --- a/lib/cassandra/cluster.rb +++ b/lib/cassandra/cluster.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/cluster/client.rb b/lib/cassandra/cluster/client.rb index 1db1da7dc..670d3f6ba 100644 --- a/lib/cassandra/cluster/client.rb +++ b/lib/cassandra/cluster/client.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/cluster/connection_pool.rb b/lib/cassandra/cluster/connection_pool.rb index 791c6c60d..262fac082 100644 --- a/lib/cassandra/cluster/connection_pool.rb +++ b/lib/cassandra/cluster/connection_pool.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/cluster/connector.rb b/lib/cassandra/cluster/connector.rb index dc446d2f5..3b73c3318 100644 --- a/lib/cassandra/cluster/connector.rb +++ b/lib/cassandra/cluster/connector.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/cluster/control_connection.rb b/lib/cassandra/cluster/control_connection.rb index 08a71354d..9ff1fcfc2 100644 --- a/lib/cassandra/cluster/control_connection.rb +++ b/lib/cassandra/cluster/control_connection.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/cluster/failed_connection.rb b/lib/cassandra/cluster/failed_connection.rb index 36f057e54..e7e6f7da6 100644 --- a/lib/cassandra/cluster/failed_connection.rb +++ b/lib/cassandra/cluster/failed_connection.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/cluster/metadata.rb b/lib/cassandra/cluster/metadata.rb index acc4b8975..c93395e34 100644 --- a/lib/cassandra/cluster/metadata.rb +++ b/lib/cassandra/cluster/metadata.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/cluster/options.rb b/lib/cassandra/cluster/options.rb index 64adc3d08..4d12dd4e0 100644 --- a/lib/cassandra/cluster/options.rb +++ b/lib/cassandra/cluster/options.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/cluster/registry.rb b/lib/cassandra/cluster/registry.rb index 692bf9bc9..debe0bfec 100644 --- a/lib/cassandra/cluster/registry.rb +++ b/lib/cassandra/cluster/registry.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/cluster/schema.rb b/lib/cassandra/cluster/schema.rb index 571573eb3..845ebbdbd 100644 --- a/lib/cassandra/cluster/schema.rb +++ b/lib/cassandra/cluster/schema.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/cluster/schema/cql_type_parser.rb b/lib/cassandra/cluster/schema/cql_type_parser.rb index f6413e768..8fc17d70c 100644 --- a/lib/cassandra/cluster/schema/cql_type_parser.rb +++ b/lib/cassandra/cluster/schema/cql_type_parser.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/cluster/schema/fetchers.rb b/lib/cassandra/cluster/schema/fetchers.rb index 6412722ea..c489a879a 100644 --- a/lib/cassandra/cluster/schema/fetchers.rb +++ b/lib/cassandra/cluster/schema/fetchers.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/cluster/schema/fqcn_type_parser.rb b/lib/cassandra/cluster/schema/fqcn_type_parser.rb index 5cd900529..ac70c2284 100644 --- a/lib/cassandra/cluster/schema/fqcn_type_parser.rb +++ b/lib/cassandra/cluster/schema/fqcn_type_parser.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/cluster/schema/partitioners.rb b/lib/cassandra/cluster/schema/partitioners.rb index 5c0abf8e8..38ced1b96 100644 --- a/lib/cassandra/cluster/schema/partitioners.rb +++ b/lib/cassandra/cluster/schema/partitioners.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/cluster/schema/partitioners/murmur3.rb b/lib/cassandra/cluster/schema/partitioners/murmur3.rb index f3de1bb0e..4146ca82e 100644 --- a/lib/cassandra/cluster/schema/partitioners/murmur3.rb +++ b/lib/cassandra/cluster/schema/partitioners/murmur3.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/cluster/schema/partitioners/ordered.rb b/lib/cassandra/cluster/schema/partitioners/ordered.rb index 611146e2c..4842bcc21 100644 --- a/lib/cassandra/cluster/schema/partitioners/ordered.rb +++ b/lib/cassandra/cluster/schema/partitioners/ordered.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/cluster/schema/partitioners/random.rb b/lib/cassandra/cluster/schema/partitioners/random.rb index 0738804a8..960bf4ddc 100644 --- a/lib/cassandra/cluster/schema/partitioners/random.rb +++ b/lib/cassandra/cluster/schema/partitioners/random.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/cluster/schema/replication_strategies.rb b/lib/cassandra/cluster/schema/replication_strategies.rb index 49f062470..0b9ee91f7 100644 --- a/lib/cassandra/cluster/schema/replication_strategies.rb +++ b/lib/cassandra/cluster/schema/replication_strategies.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/cluster/schema/replication_strategies/network_topology.rb b/lib/cassandra/cluster/schema/replication_strategies/network_topology.rb index dc0e137a2..5a85f6795 100644 --- a/lib/cassandra/cluster/schema/replication_strategies/network_topology.rb +++ b/lib/cassandra/cluster/schema/replication_strategies/network_topology.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/cluster/schema/replication_strategies/none.rb b/lib/cassandra/cluster/schema/replication_strategies/none.rb index 7bd0cab2b..0050b7657 100644 --- a/lib/cassandra/cluster/schema/replication_strategies/none.rb +++ b/lib/cassandra/cluster/schema/replication_strategies/none.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/cluster/schema/replication_strategies/simple.rb b/lib/cassandra/cluster/schema/replication_strategies/simple.rb index 0230f4180..b4d88a29f 100644 --- a/lib/cassandra/cluster/schema/replication_strategies/simple.rb +++ b/lib/cassandra/cluster/schema/replication_strategies/simple.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/column.rb b/lib/cassandra/column.rb index 2b1e3b8d7..b7617ca54 100644 --- a/lib/cassandra/column.rb +++ b/lib/cassandra/column.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/column_container.rb b/lib/cassandra/column_container.rb index 1ce5b3c1f..349b4bd76 100644 --- a/lib/cassandra/column_container.rb +++ b/lib/cassandra/column_container.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/compression.rb b/lib/cassandra/compression.rb index 4de7d6890..028ded01f 100644 --- a/lib/cassandra/compression.rb +++ b/lib/cassandra/compression.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/compression/compressors/lz4.rb b/lib/cassandra/compression/compressors/lz4.rb index 2a248f31b..dd80f2a63 100644 --- a/lib/cassandra/compression/compressors/lz4.rb +++ b/lib/cassandra/compression/compressors/lz4.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/compression/compressors/snappy.rb b/lib/cassandra/compression/compressors/snappy.rb index 4d12db419..dd170413b 100644 --- a/lib/cassandra/compression/compressors/snappy.rb +++ b/lib/cassandra/compression/compressors/snappy.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/custom_data.rb b/lib/cassandra/custom_data.rb index 25de823a8..68492b5d5 100644 --- a/lib/cassandra/custom_data.rb +++ b/lib/cassandra/custom_data.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/driver.rb b/lib/cassandra/driver.rb index f08d7a806..3e8f2b8fe 100644 --- a/lib/cassandra/driver.rb +++ b/lib/cassandra/driver.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/errors.rb b/lib/cassandra/errors.rb index ffd8d4cc5..2a6ec05d3 100644 --- a/lib/cassandra/errors.rb +++ b/lib/cassandra/errors.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/execution/info.rb b/lib/cassandra/execution/info.rb index b735eeb33..a1d4f81d0 100644 --- a/lib/cassandra/execution/info.rb +++ b/lib/cassandra/execution/info.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/execution/options.rb b/lib/cassandra/execution/options.rb index 372445eab..50e838738 100644 --- a/lib/cassandra/execution/options.rb +++ b/lib/cassandra/execution/options.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/execution/profile.rb b/lib/cassandra/execution/profile.rb index 3c3567e7a..bc7767a94 100644 --- a/lib/cassandra/execution/profile.rb +++ b/lib/cassandra/execution/profile.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/execution/profile_manager.rb b/lib/cassandra/execution/profile_manager.rb index a0a4070f3..fdc2d700e 100644 --- a/lib/cassandra/execution/profile_manager.rb +++ b/lib/cassandra/execution/profile_manager.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/execution/trace.rb b/lib/cassandra/execution/trace.rb index 949086f09..108f46518 100644 --- a/lib/cassandra/execution/trace.rb +++ b/lib/cassandra/execution/trace.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/executors.rb b/lib/cassandra/executors.rb index e07282a29..f8debda7c 100644 --- a/lib/cassandra/executors.rb +++ b/lib/cassandra/executors.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/function.rb b/lib/cassandra/function.rb index a72668a93..949126286 100644 --- a/lib/cassandra/function.rb +++ b/lib/cassandra/function.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/function_collection.rb b/lib/cassandra/function_collection.rb index 4702e65e8..ce872a39d 100644 --- a/lib/cassandra/function_collection.rb +++ b/lib/cassandra/function_collection.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/future.rb b/lib/cassandra/future.rb index ef4febe76..956a150ad 100644 --- a/lib/cassandra/future.rb +++ b/lib/cassandra/future.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/host.rb b/lib/cassandra/host.rb index e63b72a87..8b04ce88b 100644 --- a/lib/cassandra/host.rb +++ b/lib/cassandra/host.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/index.rb b/lib/cassandra/index.rb index 45ac0b6f3..f1eaa32dc 100644 --- a/lib/cassandra/index.rb +++ b/lib/cassandra/index.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/keyspace.rb b/lib/cassandra/keyspace.rb index 348250407..3a67b5959 100644 --- a/lib/cassandra/keyspace.rb +++ b/lib/cassandra/keyspace.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/listener.rb b/lib/cassandra/listener.rb index 6ca0c0e52..c4d7ad771 100644 --- a/lib/cassandra/listener.rb +++ b/lib/cassandra/listener.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/load_balancing.rb b/lib/cassandra/load_balancing.rb index 6f4c68cca..bc526e949 100644 --- a/lib/cassandra/load_balancing.rb +++ b/lib/cassandra/load_balancing.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/load_balancing/policies.rb b/lib/cassandra/load_balancing/policies.rb index 828c0b891..16a1aacd2 100644 --- a/lib/cassandra/load_balancing/policies.rb +++ b/lib/cassandra/load_balancing/policies.rb @@ -1,5 +1,5 @@ #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/load_balancing/policies/dc_aware_round_robin.rb b/lib/cassandra/load_balancing/policies/dc_aware_round_robin.rb index ced77b50e..487c961d0 100644 --- a/lib/cassandra/load_balancing/policies/dc_aware_round_robin.rb +++ b/lib/cassandra/load_balancing/policies/dc_aware_round_robin.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/load_balancing/policies/round_robin.rb b/lib/cassandra/load_balancing/policies/round_robin.rb index 8b3d2564a..040f51847 100644 --- a/lib/cassandra/load_balancing/policies/round_robin.rb +++ b/lib/cassandra/load_balancing/policies/round_robin.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/load_balancing/policies/token_aware.rb b/lib/cassandra/load_balancing/policies/token_aware.rb index 20e7ba52c..3d9328da5 100644 --- a/lib/cassandra/load_balancing/policies/token_aware.rb +++ b/lib/cassandra/load_balancing/policies/token_aware.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/load_balancing/policies/white_list.rb b/lib/cassandra/load_balancing/policies/white_list.rb index c1ea8cc73..4e2f0dacd 100644 --- a/lib/cassandra/load_balancing/policies/white_list.rb +++ b/lib/cassandra/load_balancing/policies/white_list.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/materialized_view.rb b/lib/cassandra/materialized_view.rb index 4632a1218..3200baaa7 100644 --- a/lib/cassandra/materialized_view.rb +++ b/lib/cassandra/materialized_view.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/null_logger.rb b/lib/cassandra/null_logger.rb index f6dfe6308..d19a79520 100644 --- a/lib/cassandra/null_logger.rb +++ b/lib/cassandra/null_logger.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol.rb b/lib/cassandra/protocol.rb index d9969f764..526c52386 100644 --- a/lib/cassandra/protocol.rb +++ b/lib/cassandra/protocol.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/coder.rb b/lib/cassandra/protocol/coder.rb index 13d8b6e42..ebd9d96cf 100644 --- a/lib/cassandra/protocol/coder.rb +++ b/lib/cassandra/protocol/coder.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/cql_byte_buffer.rb b/lib/cassandra/protocol/cql_byte_buffer.rb index c0acd5f34..ada91e546 100644 --- a/lib/cassandra/protocol/cql_byte_buffer.rb +++ b/lib/cassandra/protocol/cql_byte_buffer.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/cql_protocol_handler.rb b/lib/cassandra/protocol/cql_protocol_handler.rb index 705684f6a..e9d20d6c9 100644 --- a/lib/cassandra/protocol/cql_protocol_handler.rb +++ b/lib/cassandra/protocol/cql_protocol_handler.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/request.rb b/lib/cassandra/protocol/request.rb index 0d7b58f2b..6c1b0035d 100644 --- a/lib/cassandra/protocol/request.rb +++ b/lib/cassandra/protocol/request.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/requests/auth_response_request.rb b/lib/cassandra/protocol/requests/auth_response_request.rb index 6708e3402..4115b28c0 100644 --- a/lib/cassandra/protocol/requests/auth_response_request.rb +++ b/lib/cassandra/protocol/requests/auth_response_request.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/requests/batch_request.rb b/lib/cassandra/protocol/requests/batch_request.rb index 3162c6a5f..5d37f516e 100644 --- a/lib/cassandra/protocol/requests/batch_request.rb +++ b/lib/cassandra/protocol/requests/batch_request.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/requests/credentials_request.rb b/lib/cassandra/protocol/requests/credentials_request.rb index 06bc5b8f3..e476db57a 100644 --- a/lib/cassandra/protocol/requests/credentials_request.rb +++ b/lib/cassandra/protocol/requests/credentials_request.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/requests/execute_request.rb b/lib/cassandra/protocol/requests/execute_request.rb index fa5e0c667..692c051c9 100644 --- a/lib/cassandra/protocol/requests/execute_request.rb +++ b/lib/cassandra/protocol/requests/execute_request.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/requests/options_request.rb b/lib/cassandra/protocol/requests/options_request.rb index 8a5f566f9..23f73f845 100644 --- a/lib/cassandra/protocol/requests/options_request.rb +++ b/lib/cassandra/protocol/requests/options_request.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/requests/prepare_request.rb b/lib/cassandra/protocol/requests/prepare_request.rb index 9f2974688..b2c6850a0 100644 --- a/lib/cassandra/protocol/requests/prepare_request.rb +++ b/lib/cassandra/protocol/requests/prepare_request.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/requests/query_request.rb b/lib/cassandra/protocol/requests/query_request.rb index b21f9dab2..42e9c67ed 100644 --- a/lib/cassandra/protocol/requests/query_request.rb +++ b/lib/cassandra/protocol/requests/query_request.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/requests/register_request.rb b/lib/cassandra/protocol/requests/register_request.rb index 03259db5a..76c90839e 100644 --- a/lib/cassandra/protocol/requests/register_request.rb +++ b/lib/cassandra/protocol/requests/register_request.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/requests/startup_request.rb b/lib/cassandra/protocol/requests/startup_request.rb index 27d88f899..cf9be7ce2 100644 --- a/lib/cassandra/protocol/requests/startup_request.rb +++ b/lib/cassandra/protocol/requests/startup_request.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/requests/void_query_request.rb b/lib/cassandra/protocol/requests/void_query_request.rb index 749b167a3..80a972cbd 100644 --- a/lib/cassandra/protocol/requests/void_query_request.rb +++ b/lib/cassandra/protocol/requests/void_query_request.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/response.rb b/lib/cassandra/protocol/response.rb index c83dab0e2..e46187972 100644 --- a/lib/cassandra/protocol/response.rb +++ b/lib/cassandra/protocol/response.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/responses/already_exists_error_response.rb b/lib/cassandra/protocol/responses/already_exists_error_response.rb index 7a67a4d7b..670a1151b 100644 --- a/lib/cassandra/protocol/responses/already_exists_error_response.rb +++ b/lib/cassandra/protocol/responses/already_exists_error_response.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/responses/auth_challenge_response.rb b/lib/cassandra/protocol/responses/auth_challenge_response.rb index 1529752e1..f47bd1e05 100644 --- a/lib/cassandra/protocol/responses/auth_challenge_response.rb +++ b/lib/cassandra/protocol/responses/auth_challenge_response.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/responses/auth_success_response.rb b/lib/cassandra/protocol/responses/auth_success_response.rb index c42c9726e..91552870c 100644 --- a/lib/cassandra/protocol/responses/auth_success_response.rb +++ b/lib/cassandra/protocol/responses/auth_success_response.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/responses/authenticate_response.rb b/lib/cassandra/protocol/responses/authenticate_response.rb index 38a29a114..7d894ee2a 100644 --- a/lib/cassandra/protocol/responses/authenticate_response.rb +++ b/lib/cassandra/protocol/responses/authenticate_response.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/responses/error_response.rb b/lib/cassandra/protocol/responses/error_response.rb index 4b784f39a..8ea4304e6 100644 --- a/lib/cassandra/protocol/responses/error_response.rb +++ b/lib/cassandra/protocol/responses/error_response.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/responses/event_response.rb b/lib/cassandra/protocol/responses/event_response.rb index 520e69986..2308594bc 100644 --- a/lib/cassandra/protocol/responses/event_response.rb +++ b/lib/cassandra/protocol/responses/event_response.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/responses/function_failure_error_response.rb b/lib/cassandra/protocol/responses/function_failure_error_response.rb index b797254da..7d64fb416 100644 --- a/lib/cassandra/protocol/responses/function_failure_error_response.rb +++ b/lib/cassandra/protocol/responses/function_failure_error_response.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/responses/prepared_result_response.rb b/lib/cassandra/protocol/responses/prepared_result_response.rb index e8d60dcf8..5c69afde5 100644 --- a/lib/cassandra/protocol/responses/prepared_result_response.rb +++ b/lib/cassandra/protocol/responses/prepared_result_response.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/responses/raw_rows_result_response.rb b/lib/cassandra/protocol/responses/raw_rows_result_response.rb index 50e88ee8a..9f7335985 100644 --- a/lib/cassandra/protocol/responses/raw_rows_result_response.rb +++ b/lib/cassandra/protocol/responses/raw_rows_result_response.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/responses/read_failure_error_response.rb b/lib/cassandra/protocol/responses/read_failure_error_response.rb index 417009bf4..8eac8b09e 100644 --- a/lib/cassandra/protocol/responses/read_failure_error_response.rb +++ b/lib/cassandra/protocol/responses/read_failure_error_response.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/responses/read_timeout_error_response.rb b/lib/cassandra/protocol/responses/read_timeout_error_response.rb index 4b277b9a2..817c1aab9 100644 --- a/lib/cassandra/protocol/responses/read_timeout_error_response.rb +++ b/lib/cassandra/protocol/responses/read_timeout_error_response.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/responses/ready_response.rb b/lib/cassandra/protocol/responses/ready_response.rb index e91fda382..bdcc74cda 100644 --- a/lib/cassandra/protocol/responses/ready_response.rb +++ b/lib/cassandra/protocol/responses/ready_response.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/responses/result_response.rb b/lib/cassandra/protocol/responses/result_response.rb index 71f8c0fdd..42798c6f3 100644 --- a/lib/cassandra/protocol/responses/result_response.rb +++ b/lib/cassandra/protocol/responses/result_response.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/responses/rows_result_response.rb b/lib/cassandra/protocol/responses/rows_result_response.rb index a0b568dc2..bf305f631 100644 --- a/lib/cassandra/protocol/responses/rows_result_response.rb +++ b/lib/cassandra/protocol/responses/rows_result_response.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/responses/schema_change_event_response.rb b/lib/cassandra/protocol/responses/schema_change_event_response.rb index 7f6fe69a7..7927df852 100644 --- a/lib/cassandra/protocol/responses/schema_change_event_response.rb +++ b/lib/cassandra/protocol/responses/schema_change_event_response.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/responses/schema_change_result_response.rb b/lib/cassandra/protocol/responses/schema_change_result_response.rb index 43ce93cdb..7e7037f18 100644 --- a/lib/cassandra/protocol/responses/schema_change_result_response.rb +++ b/lib/cassandra/protocol/responses/schema_change_result_response.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/responses/set_keyspace_result_response.rb b/lib/cassandra/protocol/responses/set_keyspace_result_response.rb index bc39f33be..509fb2749 100644 --- a/lib/cassandra/protocol/responses/set_keyspace_result_response.rb +++ b/lib/cassandra/protocol/responses/set_keyspace_result_response.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/responses/status_change_event_response.rb b/lib/cassandra/protocol/responses/status_change_event_response.rb index 7cb4c5112..5a8e20986 100644 --- a/lib/cassandra/protocol/responses/status_change_event_response.rb +++ b/lib/cassandra/protocol/responses/status_change_event_response.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/responses/supported_response.rb b/lib/cassandra/protocol/responses/supported_response.rb index 844dbd6ed..daa3a6fe2 100644 --- a/lib/cassandra/protocol/responses/supported_response.rb +++ b/lib/cassandra/protocol/responses/supported_response.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/responses/topology_change_event_response.rb b/lib/cassandra/protocol/responses/topology_change_event_response.rb index 74a566a90..1f6493ece 100644 --- a/lib/cassandra/protocol/responses/topology_change_event_response.rb +++ b/lib/cassandra/protocol/responses/topology_change_event_response.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/responses/unavailable_error_response.rb b/lib/cassandra/protocol/responses/unavailable_error_response.rb index bc04d9352..d6c83280d 100644 --- a/lib/cassandra/protocol/responses/unavailable_error_response.rb +++ b/lib/cassandra/protocol/responses/unavailable_error_response.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/responses/unprepared_error_response.rb b/lib/cassandra/protocol/responses/unprepared_error_response.rb index b2da1a9b1..4a73119cf 100644 --- a/lib/cassandra/protocol/responses/unprepared_error_response.rb +++ b/lib/cassandra/protocol/responses/unprepared_error_response.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/responses/void_result_response.rb b/lib/cassandra/protocol/responses/void_result_response.rb index 2aaea898a..3cad53b5f 100644 --- a/lib/cassandra/protocol/responses/void_result_response.rb +++ b/lib/cassandra/protocol/responses/void_result_response.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/responses/write_failure_error_response.rb b/lib/cassandra/protocol/responses/write_failure_error_response.rb index cca4b116a..6a03eca3d 100644 --- a/lib/cassandra/protocol/responses/write_failure_error_response.rb +++ b/lib/cassandra/protocol/responses/write_failure_error_response.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/responses/write_timeout_error_response.rb b/lib/cassandra/protocol/responses/write_timeout_error_response.rb index 2354b5f43..a156a4929 100644 --- a/lib/cassandra/protocol/responses/write_timeout_error_response.rb +++ b/lib/cassandra/protocol/responses/write_timeout_error_response.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/v1.rb b/lib/cassandra/protocol/v1.rb index 092d3ec61..d7217402c 100644 --- a/lib/cassandra/protocol/v1.rb +++ b/lib/cassandra/protocol/v1.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/v3.rb b/lib/cassandra/protocol/v3.rb index 2874eaf20..96b5f15a8 100644 --- a/lib/cassandra/protocol/v3.rb +++ b/lib/cassandra/protocol/v3.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/protocol/v4.rb b/lib/cassandra/protocol/v4.rb index 754e18501..a9afe1ce3 100644 --- a/lib/cassandra/protocol/v4.rb +++ b/lib/cassandra/protocol/v4.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/reconnection.rb b/lib/cassandra/reconnection.rb index 4ddbb22dc..921dc6126 100644 --- a/lib/cassandra/reconnection.rb +++ b/lib/cassandra/reconnection.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/reconnection/policies.rb b/lib/cassandra/reconnection/policies.rb index 0e23f5e55..e4d043c7a 100644 --- a/lib/cassandra/reconnection/policies.rb +++ b/lib/cassandra/reconnection/policies.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/reconnection/policies/constant.rb b/lib/cassandra/reconnection/policies/constant.rb index 168f4c512..1a5dbc26c 100644 --- a/lib/cassandra/reconnection/policies/constant.rb +++ b/lib/cassandra/reconnection/policies/constant.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/reconnection/policies/exponential.rb b/lib/cassandra/reconnection/policies/exponential.rb index a43e7c5b4..a0d15db6a 100644 --- a/lib/cassandra/reconnection/policies/exponential.rb +++ b/lib/cassandra/reconnection/policies/exponential.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/result.rb b/lib/cassandra/result.rb index 1f124b58d..a6753c25d 100644 --- a/lib/cassandra/result.rb +++ b/lib/cassandra/result.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/retry.rb b/lib/cassandra/retry.rb index dc308529b..61761f9c0 100644 --- a/lib/cassandra/retry.rb +++ b/lib/cassandra/retry.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/retry/policies.rb b/lib/cassandra/retry/policies.rb index b58bfd1ef..bf83eeea1 100644 --- a/lib/cassandra/retry/policies.rb +++ b/lib/cassandra/retry/policies.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/retry/policies/default.rb b/lib/cassandra/retry/policies/default.rb index 905fb352d..6853f2b92 100644 --- a/lib/cassandra/retry/policies/default.rb +++ b/lib/cassandra/retry/policies/default.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/retry/policies/downgrading_consistency.rb b/lib/cassandra/retry/policies/downgrading_consistency.rb index 83ddb9842..e45a2249a 100644 --- a/lib/cassandra/retry/policies/downgrading_consistency.rb +++ b/lib/cassandra/retry/policies/downgrading_consistency.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/retry/policies/fallthrough.rb b/lib/cassandra/retry/policies/fallthrough.rb index 95bd90edd..47d15fc54 100644 --- a/lib/cassandra/retry/policies/fallthrough.rb +++ b/lib/cassandra/retry/policies/fallthrough.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/session.rb b/lib/cassandra/session.rb index 131ca0c07..71d938200 100644 --- a/lib/cassandra/session.rb +++ b/lib/cassandra/session.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/statement.rb b/lib/cassandra/statement.rb index 73071a77f..528240b47 100644 --- a/lib/cassandra/statement.rb +++ b/lib/cassandra/statement.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/statements.rb b/lib/cassandra/statements.rb index 47f31e547..ca4a4f145 100644 --- a/lib/cassandra/statements.rb +++ b/lib/cassandra/statements.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/statements/batch.rb b/lib/cassandra/statements/batch.rb index f681ec1b4..1a022c8cf 100644 --- a/lib/cassandra/statements/batch.rb +++ b/lib/cassandra/statements/batch.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/statements/bound.rb b/lib/cassandra/statements/bound.rb index 2ab7e9859..d8ac2bc34 100644 --- a/lib/cassandra/statements/bound.rb +++ b/lib/cassandra/statements/bound.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/statements/prepared.rb b/lib/cassandra/statements/prepared.rb index a9d89eb0e..bf9982a89 100644 --- a/lib/cassandra/statements/prepared.rb +++ b/lib/cassandra/statements/prepared.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/statements/simple.rb b/lib/cassandra/statements/simple.rb index ef2adff1e..aafc4bdaf 100644 --- a/lib/cassandra/statements/simple.rb +++ b/lib/cassandra/statements/simple.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/statements/void.rb b/lib/cassandra/statements/void.rb index 942ada1f9..33a887c1c 100644 --- a/lib/cassandra/statements/void.rb +++ b/lib/cassandra/statements/void.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/table.rb b/lib/cassandra/table.rb index 9ed97fb2f..5fecb09a4 100644 --- a/lib/cassandra/table.rb +++ b/lib/cassandra/table.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/time.rb b/lib/cassandra/time.rb index 602baa07e..d49105b98 100644 --- a/lib/cassandra/time.rb +++ b/lib/cassandra/time.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/time_uuid.rb b/lib/cassandra/time_uuid.rb index 577e38047..0613dfdd4 100644 --- a/lib/cassandra/time_uuid.rb +++ b/lib/cassandra/time_uuid.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/timestamp_generator.rb b/lib/cassandra/timestamp_generator.rb index 53b3a930f..8b97a6d59 100644 --- a/lib/cassandra/timestamp_generator.rb +++ b/lib/cassandra/timestamp_generator.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/timestamp_generator/simple.rb b/lib/cassandra/timestamp_generator/simple.rb index 8838e6803..045676870 100644 --- a/lib/cassandra/timestamp_generator/simple.rb +++ b/lib/cassandra/timestamp_generator/simple.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/timestamp_generator/ticking_on_duplicate.rb b/lib/cassandra/timestamp_generator/ticking_on_duplicate.rb index 576f78020..b71b2e8a0 100644 --- a/lib/cassandra/timestamp_generator/ticking_on_duplicate.rb +++ b/lib/cassandra/timestamp_generator/ticking_on_duplicate.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/trigger.rb b/lib/cassandra/trigger.rb index 63120a68a..fc574ec32 100644 --- a/lib/cassandra/trigger.rb +++ b/lib/cassandra/trigger.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/tuple.rb b/lib/cassandra/tuple.rb index 585bf0fca..37430fb76 100644 --- a/lib/cassandra/tuple.rb +++ b/lib/cassandra/tuple.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/types.rb b/lib/cassandra/types.rb index 4048ea868..520ce6740 100644 --- a/lib/cassandra/types.rb +++ b/lib/cassandra/types.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/udt.rb b/lib/cassandra/udt.rb index f7d129bd4..27f0aacca 100644 --- a/lib/cassandra/udt.rb +++ b/lib/cassandra/udt.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/util.rb b/lib/cassandra/util.rb index b1d79943a..14b97ffd6 100644 --- a/lib/cassandra/util.rb +++ b/lib/cassandra/util.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/uuid.rb b/lib/cassandra/uuid.rb index 846b879d3..c46a31437 100644 --- a/lib/cassandra/uuid.rb +++ b/lib/cassandra/uuid.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/uuid/generator.rb b/lib/cassandra/uuid/generator.rb index 3e53c1dcf..3b5eb037e 100644 --- a/lib/cassandra/uuid/generator.rb +++ b/lib/cassandra/uuid/generator.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/cassandra/version.rb b/lib/cassandra/version.rb index 623de83ac..ac015bd48 100644 --- a/lib/cassandra/version.rb +++ b/lib/cassandra/version.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/lib/datastax/cassandra.rb b/lib/datastax/cassandra.rb index 89bd1f20c..1a306f97e 100644 --- a/lib/datastax/cassandra.rb +++ b/lib/datastax/cassandra.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/address_resolution/policies/ec2_multi_region_spec.rb b/spec/cassandra/address_resolution/policies/ec2_multi_region_spec.rb index 6e228b688..ca4e6c5ca 100644 --- a/spec/cassandra/address_resolution/policies/ec2_multi_region_spec.rb +++ b/spec/cassandra/address_resolution/policies/ec2_multi_region_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/auth/providers/password_spec.rb b/spec/cassandra/auth/providers/password_spec.rb index 52548c8a9..3eebe974b 100644 --- a/spec/cassandra/auth/providers/password_spec.rb +++ b/spec/cassandra/auth/providers/password_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/cluster/client_spec.rb b/spec/cassandra/cluster/client_spec.rb index 63ca6bcff..97490fd67 100644 --- a/spec/cassandra/cluster/client_spec.rb +++ b/spec/cassandra/cluster/client_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/cluster/connection_pool_spec.rb b/spec/cassandra/cluster/connection_pool_spec.rb index 980d54a29..9db04aac3 100644 --- a/spec/cassandra/cluster/connection_pool_spec.rb +++ b/spec/cassandra/cluster/connection_pool_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/cluster/control_connection_spec.rb b/spec/cassandra/cluster/control_connection_spec.rb index 15fd1d446..b0b492356 100644 --- a/spec/cassandra/cluster/control_connection_spec.rb +++ b/spec/cassandra/cluster/control_connection_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/cluster/metadata_spec.rb b/spec/cassandra/cluster/metadata_spec.rb index bde4eaecf..eeb10b767 100644 --- a/spec/cassandra/cluster/metadata_spec.rb +++ b/spec/cassandra/cluster/metadata_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/cluster/options_spec.rb b/spec/cassandra/cluster/options_spec.rb index 4696031cd..05c4a3bec 100644 --- a/spec/cassandra/cluster/options_spec.rb +++ b/spec/cassandra/cluster/options_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/cluster/registry_spec.rb b/spec/cassandra/cluster/registry_spec.rb index 5e6b847b1..976351d44 100644 --- a/spec/cassandra/cluster/registry_spec.rb +++ b/spec/cassandra/cluster/registry_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/cluster/schema/fetchers_spec.rb b/spec/cassandra/cluster/schema/fetchers_spec.rb index 28303d1dc..bacb96f3c 100644 --- a/spec/cassandra/cluster/schema/fetchers_spec.rb +++ b/spec/cassandra/cluster/schema/fetchers_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/cluster/schema/fqcn_type_parser_spec.rb b/spec/cassandra/cluster/schema/fqcn_type_parser_spec.rb index 50ff7e256..b62a5cde2 100644 --- a/spec/cassandra/cluster/schema/fqcn_type_parser_spec.rb +++ b/spec/cassandra/cluster/schema/fqcn_type_parser_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/cluster/schema/partitioners/murmur3_spec.rb b/spec/cassandra/cluster/schema/partitioners/murmur3_spec.rb index de3947a7a..d4b2cc36e 100644 --- a/spec/cassandra/cluster/schema/partitioners/murmur3_spec.rb +++ b/spec/cassandra/cluster/schema/partitioners/murmur3_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/cluster/schema/partitioners/random_spec.rb b/spec/cassandra/cluster/schema/partitioners/random_spec.rb index 366df13ec..469fa4369 100644 --- a/spec/cassandra/cluster/schema/partitioners/random_spec.rb +++ b/spec/cassandra/cluster/schema/partitioners/random_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/cluster/schema/replication_strategies/network_topology_spec.rb b/spec/cassandra/cluster/schema/replication_strategies/network_topology_spec.rb index c66717c6a..0890fc4b6 100644 --- a/spec/cassandra/cluster/schema/replication_strategies/network_topology_spec.rb +++ b/spec/cassandra/cluster/schema/replication_strategies/network_topology_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/cluster/schema/replication_strategies/none_spec.rb b/spec/cassandra/cluster/schema/replication_strategies/none_spec.rb index 6e97f97eb..f001ee77b 100644 --- a/spec/cassandra/cluster/schema/replication_strategies/none_spec.rb +++ b/spec/cassandra/cluster/schema/replication_strategies/none_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/cluster/schema_spec.rb b/spec/cassandra/cluster/schema_spec.rb index 420e27738..88703d5a2 100644 --- a/spec/cassandra/cluster/schema_spec.rb +++ b/spec/cassandra/cluster/schema_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/cluster_spec.rb b/spec/cassandra/cluster_spec.rb index 1062dead1..962afaf61 100644 --- a/spec/cassandra/cluster_spec.rb +++ b/spec/cassandra/cluster_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/compression/common.rb b/spec/cassandra/compression/common.rb index b04846527..95696b888 100644 --- a/spec/cassandra/compression/common.rb +++ b/spec/cassandra/compression/common.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/compression/compressors/lz4_spec.rb b/spec/cassandra/compression/compressors/lz4_spec.rb index b75dab868..dfb54e46e 100644 --- a/spec/cassandra/compression/compressors/lz4_spec.rb +++ b/spec/cassandra/compression/compressors/lz4_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/compression/compressors/snappy_spec.rb b/spec/cassandra/compression/compressors/snappy_spec.rb index 668cf899a..0740841b5 100644 --- a/spec/cassandra/compression/compressors/snappy_spec.rb +++ b/spec/cassandra/compression/compressors/snappy_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/execution/options_spec.rb b/spec/cassandra/execution/options_spec.rb index ea57672d1..e24757801 100644 --- a/spec/cassandra/execution/options_spec.rb +++ b/spec/cassandra/execution/options_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/execution/profile_manager_spec.rb b/spec/cassandra/execution/profile_manager_spec.rb index 57351aafe..0fc6a2f78 100644 --- a/spec/cassandra/execution/profile_manager_spec.rb +++ b/spec/cassandra/execution/profile_manager_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/execution/profile_spec.rb b/spec/cassandra/execution/profile_spec.rb index ecb652cd9..653216e8f 100644 --- a/spec/cassandra/execution/profile_spec.rb +++ b/spec/cassandra/execution/profile_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/execution/trace_spec.rb b/spec/cassandra/execution/trace_spec.rb index c0d85a91f..a855cfd48 100644 --- a/spec/cassandra/execution/trace_spec.rb +++ b/spec/cassandra/execution/trace_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/executors/same_thread_spec.rb b/spec/cassandra/executors/same_thread_spec.rb index f656b22a9..84cdcedba 100644 --- a/spec/cassandra/executors/same_thread_spec.rb +++ b/spec/cassandra/executors/same_thread_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/executors/thread_pool_spec.rb b/spec/cassandra/executors/thread_pool_spec.rb index 11aa7322b..021b49d1c 100644 --- a/spec/cassandra/executors/thread_pool_spec.rb +++ b/spec/cassandra/executors/thread_pool_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/future_spec.rb b/spec/cassandra/future_spec.rb index 781909839..38e4b3841 100644 --- a/spec/cassandra/future_spec.rb +++ b/spec/cassandra/future_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/index_spec.rb b/spec/cassandra/index_spec.rb index 8e973c83e..fe46a3b4b 100644 --- a/spec/cassandra/index_spec.rb +++ b/spec/cassandra/index_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/keyspace_spec.rb b/spec/cassandra/keyspace_spec.rb index 9509fb9c4..ebab00423 100644 --- a/spec/cassandra/keyspace_spec.rb +++ b/spec/cassandra/keyspace_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/load_balancing/policies/dc_aware_round_robin_spec.rb b/spec/cassandra/load_balancing/policies/dc_aware_round_robin_spec.rb index 17053d0c4..0e297f13b 100644 --- a/spec/cassandra/load_balancing/policies/dc_aware_round_robin_spec.rb +++ b/spec/cassandra/load_balancing/policies/dc_aware_round_robin_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/load_balancing/policies/round_robin_spec.rb b/spec/cassandra/load_balancing/policies/round_robin_spec.rb index c314c5d93..557336228 100644 --- a/spec/cassandra/load_balancing/policies/round_robin_spec.rb +++ b/spec/cassandra/load_balancing/policies/round_robin_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/load_balancing/policies/token_aware_spec.rb b/spec/cassandra/load_balancing/policies/token_aware_spec.rb index 578502d8c..c37cdb882 100644 --- a/spec/cassandra/load_balancing/policies/token_aware_spec.rb +++ b/spec/cassandra/load_balancing/policies/token_aware_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/load_balancing/policies/white_list_spec.rb b/spec/cassandra/load_balancing/policies/white_list_spec.rb index 960053196..92e936c60 100644 --- a/spec/cassandra/load_balancing/policies/white_list_spec.rb +++ b/spec/cassandra/load_balancing/policies/white_list_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/materialized_view_spec.rb b/spec/cassandra/materialized_view_spec.rb index 3c6362c44..206889cb4 100644 --- a/spec/cassandra/materialized_view_spec.rb +++ b/spec/cassandra/materialized_view_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/murmur3_spec.rb b/spec/cassandra/murmur3_spec.rb index f12e578ee..767979be8 100644 --- a/spec/cassandra/murmur3_spec.rb +++ b/spec/cassandra/murmur3_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/protocol/coder_spec.rb b/spec/cassandra/protocol/coder_spec.rb index a7b6b4b16..37408adbe 100644 --- a/spec/cassandra/protocol/coder_spec.rb +++ b/spec/cassandra/protocol/coder_spec.rb @@ -1,7 +1,7 @@ # encoding: ascii-8bit #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/protocol/cql_byte_buffer_spec.rb b/spec/cassandra/protocol/cql_byte_buffer_spec.rb index 676ba2f8c..4e3c013c4 100644 --- a/spec/cassandra/protocol/cql_byte_buffer_spec.rb +++ b/spec/cassandra/protocol/cql_byte_buffer_spec.rb @@ -1,7 +1,7 @@ # encoding: ascii-8bit #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/protocol/cql_protocol_handler_spec.rb b/spec/cassandra/protocol/cql_protocol_handler_spec.rb index 2a8371a6f..3337071b3 100644 --- a/spec/cassandra/protocol/cql_protocol_handler_spec.rb +++ b/spec/cassandra/protocol/cql_protocol_handler_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/protocol/requests/auth_response_request_spec.rb b/spec/cassandra/protocol/requests/auth_response_request_spec.rb index b261eeb99..135c3dff9 100644 --- a/spec/cassandra/protocol/requests/auth_response_request_spec.rb +++ b/spec/cassandra/protocol/requests/auth_response_request_spec.rb @@ -1,7 +1,7 @@ # encoding: ascii-8bit #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/protocol/requests/batch_request_spec.rb b/spec/cassandra/protocol/requests/batch_request_spec.rb index fd63e683c..49211e738 100644 --- a/spec/cassandra/protocol/requests/batch_request_spec.rb +++ b/spec/cassandra/protocol/requests/batch_request_spec.rb @@ -1,7 +1,7 @@ # encoding: ascii-8bit #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/protocol/requests/credentials_request_spec.rb b/spec/cassandra/protocol/requests/credentials_request_spec.rb index 7884622e8..db8410538 100644 --- a/spec/cassandra/protocol/requests/credentials_request_spec.rb +++ b/spec/cassandra/protocol/requests/credentials_request_spec.rb @@ -1,7 +1,7 @@ # encoding: ascii-8bit #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/protocol/requests/execute_request_spec.rb b/spec/cassandra/protocol/requests/execute_request_spec.rb index ac8b14f06..3f326d409 100644 --- a/spec/cassandra/protocol/requests/execute_request_spec.rb +++ b/spec/cassandra/protocol/requests/execute_request_spec.rb @@ -1,7 +1,7 @@ # encoding: ascii-8bit #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/protocol/requests/options_request_spec.rb b/spec/cassandra/protocol/requests/options_request_spec.rb index ef3830f89..ad10aa013 100644 --- a/spec/cassandra/protocol/requests/options_request_spec.rb +++ b/spec/cassandra/protocol/requests/options_request_spec.rb @@ -1,7 +1,7 @@ # encoding: ascii-8bit #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/protocol/requests/prepare_request_spec.rb b/spec/cassandra/protocol/requests/prepare_request_spec.rb index 4dc19aa04..83c30a8f1 100644 --- a/spec/cassandra/protocol/requests/prepare_request_spec.rb +++ b/spec/cassandra/protocol/requests/prepare_request_spec.rb @@ -1,7 +1,7 @@ # encoding: ascii-8bit #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/protocol/requests/query_request_spec.rb b/spec/cassandra/protocol/requests/query_request_spec.rb index dc04e803b..31f2ff707 100644 --- a/spec/cassandra/protocol/requests/query_request_spec.rb +++ b/spec/cassandra/protocol/requests/query_request_spec.rb @@ -1,7 +1,7 @@ # encoding: ascii-8bit #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/protocol/requests/register_request_spec.rb b/spec/cassandra/protocol/requests/register_request_spec.rb index 16c4be3a8..f08427cde 100644 --- a/spec/cassandra/protocol/requests/register_request_spec.rb +++ b/spec/cassandra/protocol/requests/register_request_spec.rb @@ -1,7 +1,7 @@ # encoding: ascii-8bit #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/protocol/requests/startup_request_spec.rb b/spec/cassandra/protocol/requests/startup_request_spec.rb index 3dbf70960..bbb0bd184 100644 --- a/spec/cassandra/protocol/requests/startup_request_spec.rb +++ b/spec/cassandra/protocol/requests/startup_request_spec.rb @@ -1,7 +1,7 @@ # encoding: ascii-8bit #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/protocol/responses/auth_challenge_response_spec.rb b/spec/cassandra/protocol/responses/auth_challenge_response_spec.rb index dafc1672e..fde4122a7 100644 --- a/spec/cassandra/protocol/responses/auth_challenge_response_spec.rb +++ b/spec/cassandra/protocol/responses/auth_challenge_response_spec.rb @@ -1,7 +1,7 @@ # encoding: ascii-8bit #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/protocol/responses/auth_success_response_spec.rb b/spec/cassandra/protocol/responses/auth_success_response_spec.rb index beddee03f..694a2d4c2 100644 --- a/spec/cassandra/protocol/responses/auth_success_response_spec.rb +++ b/spec/cassandra/protocol/responses/auth_success_response_spec.rb @@ -1,7 +1,7 @@ # encoding: ascii-8bit #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/protocol/responses/authenticate_response_spec.rb b/spec/cassandra/protocol/responses/authenticate_response_spec.rb index e0e2ae4db..bf10c1f7f 100644 --- a/spec/cassandra/protocol/responses/authenticate_response_spec.rb +++ b/spec/cassandra/protocol/responses/authenticate_response_spec.rb @@ -1,7 +1,7 @@ # encoding: ascii-8bit #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/protocol/responses/error_response_spec.rb b/spec/cassandra/protocol/responses/error_response_spec.rb index 0e9704b0b..c81364055 100644 --- a/spec/cassandra/protocol/responses/error_response_spec.rb +++ b/spec/cassandra/protocol/responses/error_response_spec.rb @@ -1,7 +1,7 @@ # encoding: ascii-8bit #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/protocol/responses/prepared_result_response_spec.rb b/spec/cassandra/protocol/responses/prepared_result_response_spec.rb index f4d5ea369..85b8b7a7a 100644 --- a/spec/cassandra/protocol/responses/prepared_result_response_spec.rb +++ b/spec/cassandra/protocol/responses/prepared_result_response_spec.rb @@ -1,7 +1,7 @@ # encoding: ascii-8bit #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/protocol/responses/raw_rows_result_response_spec.rb b/spec/cassandra/protocol/responses/raw_rows_result_response_spec.rb index a8f8a03dd..6f074dab1 100644 --- a/spec/cassandra/protocol/responses/raw_rows_result_response_spec.rb +++ b/spec/cassandra/protocol/responses/raw_rows_result_response_spec.rb @@ -1,7 +1,7 @@ # encoding: ascii-8bit #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/protocol/responses/ready_response_spec.rb b/spec/cassandra/protocol/responses/ready_response_spec.rb index 43711558b..2e974bd20 100644 --- a/spec/cassandra/protocol/responses/ready_response_spec.rb +++ b/spec/cassandra/protocol/responses/ready_response_spec.rb @@ -1,7 +1,7 @@ # encoding: ascii-8bit #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/protocol/responses/rows_result_response_spec.rb b/spec/cassandra/protocol/responses/rows_result_response_spec.rb index d220cc466..e0e95c1f9 100644 --- a/spec/cassandra/protocol/responses/rows_result_response_spec.rb +++ b/spec/cassandra/protocol/responses/rows_result_response_spec.rb @@ -1,7 +1,7 @@ # encoding: ascii-8bit #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/protocol/responses/schema_change_event_response_spec.rb b/spec/cassandra/protocol/responses/schema_change_event_response_spec.rb index 65c49a943..c189a0d2c 100644 --- a/spec/cassandra/protocol/responses/schema_change_event_response_spec.rb +++ b/spec/cassandra/protocol/responses/schema_change_event_response_spec.rb @@ -1,7 +1,7 @@ # encoding: ascii-8bit #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/protocol/responses/schema_change_result_response_spec.rb b/spec/cassandra/protocol/responses/schema_change_result_response_spec.rb index 90b6e5240..7e0b8a368 100644 --- a/spec/cassandra/protocol/responses/schema_change_result_response_spec.rb +++ b/spec/cassandra/protocol/responses/schema_change_result_response_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/protocol/responses/set_keyspace_result_response_spec.rb b/spec/cassandra/protocol/responses/set_keyspace_result_response_spec.rb index c4bcc0c34..bb60f3c8a 100644 --- a/spec/cassandra/protocol/responses/set_keyspace_result_response_spec.rb +++ b/spec/cassandra/protocol/responses/set_keyspace_result_response_spec.rb @@ -1,7 +1,7 @@ # encoding: ascii-8bit #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/protocol/responses/status_change_event_response_spec.rb b/spec/cassandra/protocol/responses/status_change_event_response_spec.rb index b47e6d6a9..aff24c6e3 100644 --- a/spec/cassandra/protocol/responses/status_change_event_response_spec.rb +++ b/spec/cassandra/protocol/responses/status_change_event_response_spec.rb @@ -1,7 +1,7 @@ # encoding: ascii-8bit #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/protocol/responses/supported_response_spec.rb b/spec/cassandra/protocol/responses/supported_response_spec.rb index fd961363a..691646112 100644 --- a/spec/cassandra/protocol/responses/supported_response_spec.rb +++ b/spec/cassandra/protocol/responses/supported_response_spec.rb @@ -1,7 +1,7 @@ # encoding: ascii-8bit #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/protocol/responses/topology_change_event_response_spec.rb b/spec/cassandra/protocol/responses/topology_change_event_response_spec.rb index cafb48e33..e55532d59 100644 --- a/spec/cassandra/protocol/responses/topology_change_event_response_spec.rb +++ b/spec/cassandra/protocol/responses/topology_change_event_response_spec.rb @@ -1,7 +1,7 @@ # encoding: ascii-8bit #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/protocol/responses/void_result_response_spec.rb b/spec/cassandra/protocol/responses/void_result_response_spec.rb index 4ab5a73f1..e3b780b2b 100644 --- a/spec/cassandra/protocol/responses/void_result_response_spec.rb +++ b/spec/cassandra/protocol/responses/void_result_response_spec.rb @@ -1,7 +1,7 @@ # encoding: ascii-8bit #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/protocol/v1/decoder_spec.rb b/spec/cassandra/protocol/v1/decoder_spec.rb index 02a03e7b6..f685b8cfe 100644 --- a/spec/cassandra/protocol/v1/decoder_spec.rb +++ b/spec/cassandra/protocol/v1/decoder_spec.rb @@ -1,7 +1,7 @@ # encoding: ascii-8bit #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/protocol/v3/decoder_spec.rb b/spec/cassandra/protocol/v3/decoder_spec.rb index eb717ee5e..c5c362a56 100644 --- a/spec/cassandra/protocol/v3/decoder_spec.rb +++ b/spec/cassandra/protocol/v3/decoder_spec.rb @@ -1,7 +1,7 @@ # encoding: ascii-8bit #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/protocol/v4/decoder_spec.rb b/spec/cassandra/protocol/v4/decoder_spec.rb index 5957a15f1..b8ca1f6d3 100644 --- a/spec/cassandra/protocol/v4/decoder_spec.rb +++ b/spec/cassandra/protocol/v4/decoder_spec.rb @@ -1,7 +1,7 @@ # encoding: ascii-8bit #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/retry/policies/default_spec.rb b/spec/cassandra/retry/policies/default_spec.rb index d7a423a04..0422593e6 100644 --- a/spec/cassandra/retry/policies/default_spec.rb +++ b/spec/cassandra/retry/policies/default_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/retry/policies/downgrading_consistency_spec.rb b/spec/cassandra/retry/policies/downgrading_consistency_spec.rb index ab092ee65..79294d129 100644 --- a/spec/cassandra/retry/policies/downgrading_consistency_spec.rb +++ b/spec/cassandra/retry/policies/downgrading_consistency_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/retry/policies/fallthrough_spec.rb b/spec/cassandra/retry/policies/fallthrough_spec.rb index 0fb984431..785d66b36 100644 --- a/spec/cassandra/retry/policies/fallthrough_spec.rb +++ b/spec/cassandra/retry/policies/fallthrough_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/session_spec.rb b/spec/cassandra/session_spec.rb index 502c64b25..2cc0cb2bf 100644 --- a/spec/cassandra/session_spec.rb +++ b/spec/cassandra/session_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/table_spec.rb b/spec/cassandra/table_spec.rb index 60180f587..d71d24442 100644 --- a/spec/cassandra/table_spec.rb +++ b/spec/cassandra/table_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/time_uuid_spec.rb b/spec/cassandra/time_uuid_spec.rb index 3aa1be2e1..bb85fb717 100644 --- a/spec/cassandra/time_uuid_spec.rb +++ b/spec/cassandra/time_uuid_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/util_spec.rb b/spec/cassandra/util_spec.rb index a542b3ca1..fe344a7b0 100644 --- a/spec/cassandra/util_spec.rb +++ b/spec/cassandra/util_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/uuid/generator_spec.rb b/spec/cassandra/uuid/generator_spec.rb index 2facb088e..465303c9c 100644 --- a/spec/cassandra/uuid/generator_spec.rb +++ b/spec/cassandra/uuid/generator_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/cassandra/uuid_spec.rb b/spec/cassandra/uuid_spec.rb index cf2af8c27..8a998a684 100644 --- a/spec/cassandra/uuid_spec.rb +++ b/spec/cassandra/uuid_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/regressions/RUBY-189_spec.rb b/spec/regressions/RUBY-189_spec.rb index c6ac9ae79..91babeb4b 100644 --- a/spec/regressions/RUBY-189_spec.rb +++ b/spec/regressions/RUBY-189_spec.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 1e44c6f7b..801fe2a2c 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/support/await_helper.rb b/spec/support/await_helper.rb index 903475eb4..d1a85ab8e 100644 --- a/spec/support/await_helper.rb +++ b/spec/support/await_helper.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/support/bytes_helper.rb b/spec/support/bytes_helper.rb index 81ca1faf2..f465b9a97 100644 --- a/spec/support/bytes_helper.rb +++ b/spec/support/bytes_helper.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/support/fake_cluster_registry.rb b/spec/support/fake_cluster_registry.rb index b604a20ad..a6c72c467 100644 --- a/spec/support/fake_cluster_registry.rb +++ b/spec/support/fake_cluster_registry.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/support/fake_io_reactor.rb b/spec/support/fake_io_reactor.rb index 7cf5f0072..33068c753 100644 --- a/spec/support/fake_io_reactor.rb +++ b/spec/support/fake_io_reactor.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/support/stub_io_reactor.rb b/spec/support/stub_io_reactor.rb index 2494a1338..9cc8214b5 100644 --- a/spec/support/stub_io_reactor.rb +++ b/spec/support/stub_io_reactor.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/support/ccm.rb b/support/ccm.rb index 54b6a67b8..c7ad009c0 100644 --- a/support/ccm.rb +++ b/support/ccm.rb @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/support/triggers/AuditTrigger.properties b/support/triggers/AuditTrigger.properties index a8ccc51e9..a0e5f2011 100644 --- a/support/triggers/AuditTrigger.properties +++ b/support/triggers/AuditTrigger.properties @@ -1,7 +1,7 @@ # encoding: utf-8 #-- -# Copyright 2013-2016 DataStax, Inc. +# Copyright 2013-2017 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. From 4087069a7c7d8ef849e975492f8cf272fb98958f Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 15 May 2017 11:11:56 -0700 Subject: [PATCH 190/196] Update README and miscellaneous files in preparation of 3.2.0 release --- Gemfile.lock | 2 +- README.md | 8 ++++---- docs.yaml | 2 ++ lib/cassandra/version.rb | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index bfc005571..5049a337b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - cassandra-driver (3.1.1.rc.1) + cassandra-driver (3.2.0) ione (~> 1.2) GEM diff --git a/README.md b/README.md index f1ac9e721..a5e78ebe2 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ __Note__: if you want to use compression you should also install [snappy](http:/ 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) +interface parity with cql-rb](https://github.com/datastax/ruby-driver/blob/v3.2.0/examples/cql-rb-wrapper.rb) to assist you with gradual upgrade. If you are upgrading to DataStax Enterprise, use the [Ruby DSE driver](https://github.com/datastax/ruby-dse-driver.git) @@ -109,7 +109,7 @@ for more features and better compatibility. ## What's new in v3.2 This minor release adds support for MRI 2.4.x and also contains a few miscellaneous defect fixes. -See the [changelog](https://github.com/datastax/ruby-driver/blob/master/CHANGELOG.md) for more information on all +See the [changelog](https://github.com/datastax/ruby-driver/blob/v3.2.0/CHANGELOG.md) for more information on all changes in this version and past versions. ## What's new in v3.1 @@ -177,7 +177,7 @@ examples in the `features/` directory. ## Running tests If you don't feel like reading through the following instructions on how to run -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). +ruby-driver tests, feel free to [check out .travis.yml for the entire build code](https://github.com/datastax/ruby-driver/blob/v3.2.0/.travis.yml). * Check out the driver codebase and install test dependencies: @@ -201,7 +201,7 @@ CASSANDRA_VERSION=2.1.12 bundle exec rake test # run both as well as integration ## Changelog & versioning Check out the [releases on GitHub](https://github.com/datastax/ruby-driver/releases) and -[changelog](https://github.com/datastax/ruby-driver/blob/master/CHANGELOG.md). Version +[changelog](https://github.com/datastax/ruby-driver/blob/v3.2.0/CHANGELOG.md). Version numbering follows the [semantic versioning](http://semver.org/) scheme. Private and experimental APIs, defined as whatever is not in the diff --git a/docs.yaml b/docs.yaml index ca70a3536..29abb7c00 100644 --- a/docs.yaml +++ b/docs.yaml @@ -41,6 +41,8 @@ links: - title: Releases href: https://github.com/datastax/ruby-driver/releases versions: + - name: 3.2 + ref: v3.2.0 - name: 3.1 ref: v3.1.0 - name: 3.0 diff --git a/lib/cassandra/version.rb b/lib/cassandra/version.rb index ac015bd48..f7cecaf25 100644 --- a/lib/cassandra/version.rb +++ b/lib/cassandra/version.rb @@ -17,5 +17,5 @@ #++ module Cassandra - VERSION = '3.1.1.rc.1'.freeze + VERSION = '3.2.0'.freeze end From b8ee9053771e2d1dc0fef6ce77fe9841697578f5 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 15 May 2017 12:09:45 -0700 Subject: [PATCH 191/196] Reset miscellaneous files for a potential 3.2.1.rc.1 release --- CHANGELOG.md | 2 ++ Gemfile.lock | 2 +- README.md | 8 ++++---- lib/cassandra/version.rb | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c49a9aa9..a52faa26e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +# master + # 3.2.0 Features: diff --git a/Gemfile.lock b/Gemfile.lock index 5049a337b..28bb80d78 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - cassandra-driver (3.2.0) + cassandra-driver (3.2.1.rc.1) ione (~> 1.2) GEM diff --git a/README.md b/README.md index a5e78ebe2..f1ac9e721 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ __Note__: if you want to use compression you should also install [snappy](http:/ 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.2.0/examples/cql-rb-wrapper.rb) +interface parity with cql-rb](https://github.com/datastax/ruby-driver/blob/master/examples/cql-rb-wrapper.rb) to assist you with gradual upgrade. If you are upgrading to DataStax Enterprise, use the [Ruby DSE driver](https://github.com/datastax/ruby-dse-driver.git) @@ -109,7 +109,7 @@ for more features and better compatibility. ## What's new in v3.2 This minor release adds support for MRI 2.4.x and also contains a few miscellaneous defect fixes. -See the [changelog](https://github.com/datastax/ruby-driver/blob/v3.2.0/CHANGELOG.md) for more information on all +See the [changelog](https://github.com/datastax/ruby-driver/blob/master/CHANGELOG.md) for more information on all changes in this version and past versions. ## What's new in v3.1 @@ -177,7 +177,7 @@ examples in the `features/` directory. ## Running tests If you don't feel like reading through the following instructions on how to run -ruby-driver tests, feel free to [check out .travis.yml for the entire build code](https://github.com/datastax/ruby-driver/blob/v3.2.0/.travis.yml). +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). * Check out the driver codebase and install test dependencies: @@ -201,7 +201,7 @@ CASSANDRA_VERSION=2.1.12 bundle exec rake test # run both as well as integration ## Changelog & versioning Check out the [releases on GitHub](https://github.com/datastax/ruby-driver/releases) and -[changelog](https://github.com/datastax/ruby-driver/blob/v3.2.0/CHANGELOG.md). Version +[changelog](https://github.com/datastax/ruby-driver/blob/master/CHANGELOG.md). Version numbering follows the [semantic versioning](http://semver.org/) scheme. Private and experimental APIs, defined as whatever is not in the diff --git a/lib/cassandra/version.rb b/lib/cassandra/version.rb index f7cecaf25..df2fd7988 100644 --- a/lib/cassandra/version.rb +++ b/lib/cassandra/version.rb @@ -17,5 +17,5 @@ #++ module Cassandra - VERSION = '3.2.0'.freeze + VERSION = '3.2.1.rc.1'.freeze end From 3c2a98fae7f66270c83a449a208c0ca2dbc35c13 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 15 May 2017 15:10:39 -0700 Subject: [PATCH 192/196] Fixed incorrect links in CHANGELOG.md --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a52faa26e..fee81e140 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,11 +3,11 @@ # 3.2.0 Features: -* [RUBY-294](https://datastax-oss.atlassian.net/browse/RUBY-291) Support MRI 2.4.x. Thanks, @lautis, for this contribution! +* [RUBY-294](https://datastax-oss.atlassian.net/browse/RUBY-294) Support MRI 2.4.x. Thanks, @lautis, for this contribution! Bug Fixes: * [RUBY-291](https://datastax-oss.atlassian.net/browse/RUBY-291) Driver fails to connect to cluster when a table column type has a quoted name. -* [RUBY-292](https://datastax-oss.atlassian.net/browse/RUBY-291) Driver sporadically crashes with "undefined method 'ip'" error. +* [RUBY-292](https://datastax-oss.atlassian.net/browse/RUBY-292) Driver sporadically crashes with "undefined method 'ip'" error. Thanks, @grosser, for the fix! * [RUBY-295](https://datastax-oss.atlassian.net/browse/RUBY-295) When a custom address resolver is configured, consult it when handling all host events, and thus prevent the creation of invalid Host objects. From 849df288188c32a2c51ad06f74c18fcadab4e4f4 Mon Sep 17 00:00:00 2001 From: Sandeep Tamhankar Date: Mon, 24 Jul 2017 18:03:58 -0700 Subject: [PATCH 193/196] Added disclaimer about running on big-endian systems --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index f1ac9e721..bb75c944c 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,8 @@ This driver works exclusively with the Cassandra Query Language v3 (CQL3) and Ca __Note__: Rubinius is not supported. MRI 2.0, and 2.1 are not officially supported, but they should work. MRI 1.9.3 is deprecated and may break in any release after 3.0. +__Note__: Big-endian systems are not supported. + ## Feedback Requested *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). From 237351550814ebbcf303c1fda968f8b38934855d Mon Sep 17 00:00:00 2001 From: Brian Durand Date: Tue, 25 Jul 2017 15:20:28 -0400 Subject: [PATCH 194/196] remove custom message on options assertion (#232) --- lib/cassandra/session.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cassandra/session.rb b/lib/cassandra/session.rb index 71d938200..23a39d231 100644 --- a/lib/cassandra/session.rb +++ b/lib/cassandra/session.rb @@ -243,7 +243,7 @@ def inspect # @private def merge_execution_options(options) if options - Util.assert_instance_of(::Hash, options, "options must be a Hash, #{options.inspect} given") + Util.assert_instance_of(::Hash, options) # Yell if the caller gave us a bad profile name. execution_profile = nil if options.key?(:execution_profile) From 3db2befadcc2d0af1669d4b9d8fe22ce114baf70 Mon Sep 17 00:00:00 2001 From: Kevin Thomas Date: Mon, 4 Sep 2017 17:14:10 -0700 Subject: [PATCH 195/196] Fixed a typo in the 'features' README (#233) --- features/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/README.md b/features/README.md index e190401cb..b5d4713c9 100644 --- a/features/README.md +++ b/features/README.md @@ -80,7 +80,7 @@ statement = session.prepare('INSERT INTO users (username, email) VALUES (?, ?)') session.execute(statement, arguments: ['avalanche123', 'bulat.shakirzyanov@datastax.com']) ``` -You should prepare a statement for a given query **only** once and then resue it by calling #execute. Re-preparing the same statement will have a negative impact on the performance and should be avoided. +You should prepare a statement for a given query **only** once and then reuse it by calling #execute. Re-preparing the same statement will have a negative impact on the performance and should be avoided. A prepared statement can be run many times, but the CQL parsing will only be done once on each node. Use prepared statements for queries you run over and over again. From 615cf3929ff93f599eb0c09ca8f07742f0b8bfa8 Mon Sep 17 00:00:00 2001 From: Evan Prothro Date: Wed, 20 Sep 2017 15:36:42 -0500 Subject: [PATCH 196/196] wip: 3380@100i vs 3090@100i --- bin/console | 15 +++ lib/cassandra/protocol.rb | 4 + lib/cassandra/protocol/builders.rb | 34 +++++++ .../protocol/builders/hash_builder.rb | 38 ++++++++ lib/cassandra/protocol/builders/registry.rb | 94 +++++++++++++++++++ lib/cassandra/protocol/coder.rb | 6 +- 6 files changed, 189 insertions(+), 2 deletions(-) create mode 100755 bin/console create mode 100644 lib/cassandra/protocol/builders.rb create mode 100644 lib/cassandra/protocol/builders/hash_builder.rb create mode 100644 lib/cassandra/protocol/builders/registry.rb diff --git a/bin/console b/bin/console new file mode 100755 index 000000000..e10ffd76a --- /dev/null +++ b/bin/console @@ -0,0 +1,15 @@ +#!/usr/bin/env ruby + +require "bundler/setup" +require "cassandra" + +# You can add fixtures and/or initialization code here to make experimenting +# with your gem easier. You can also use a different console, if you like. + +begin + require "pry" + Pry.start +rescue LoadError + require "irb" + IRB.start +end diff --git a/lib/cassandra/protocol.rb b/lib/cassandra/protocol.rb index 526c52386..a3136734e 100644 --- a/lib/cassandra/protocol.rb +++ b/lib/cassandra/protocol.rb @@ -100,3 +100,7 @@ module Versions require 'cassandra/protocol/v3' require 'cassandra/protocol/v4' require 'cassandra/protocol/coder' +require 'cassandra/protocol/builders' +require 'cassandra/protocol/builders/registry' +require 'cassandra/protocol/builders/hash_builder' + diff --git a/lib/cassandra/protocol/builders.rb b/lib/cassandra/protocol/builders.rb new file mode 100644 index 000000000..b6e465c1d --- /dev/null +++ b/lib/cassandra/protocol/builders.rb @@ -0,0 +1,34 @@ +# encoding: utf-8 + +#-- +# Copyright 2013-2017 DataStax, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + +module Cassandra + module Protocol + module Builders + module_function + + def row_registry + @row_registry ||= Registry.new(HashBuilder) + end + + # def udt_registry + # @row_registry ||= Registry.new(UDTBuilder) + # end + + end + end +end \ No newline at end of file diff --git a/lib/cassandra/protocol/builders/hash_builder.rb b/lib/cassandra/protocol/builders/hash_builder.rb new file mode 100644 index 000000000..2e7943c9c --- /dev/null +++ b/lib/cassandra/protocol/builders/hash_builder.rb @@ -0,0 +1,38 @@ +# encoding: utf-8 + +#-- +# Copyright 2013-2017 DataStax, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + +module Cassandra + module Protocol + module Builders + class HashBuilder + + def initialize + @hash = Hash.new + end + + def []=(k, v) + @hash[k] = v + end + + def build + @hash + end + end + end + end +end diff --git a/lib/cassandra/protocol/builders/registry.rb b/lib/cassandra/protocol/builders/registry.rb new file mode 100644 index 000000000..4eaa32e72 --- /dev/null +++ b/lib/cassandra/protocol/builders/registry.rb @@ -0,0 +1,94 @@ +# encoding: utf-8 + +#-- +# Copyright 2013-2017 DataStax, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + +module Cassandra + module Protocol + module Builders + # @private + class Registry + include MonitorMixin + + def initialize(default_builder) + @builders = ::Hash.new + + mon_initialize + self.default = default_builder + end + + def fetch(keyspace, identifier) + @builders[determine_key(keyspace, identifier)] + end + + def add(keyspace, identifier, builder) + validate_builder(builder) + key = determine_key(keyspace, identifier) + + synchronize do + builders = @builders.dup + builders[key] = builder + @builders = builders + end + + self + end + + def remove(keyspace, identifier) + key = determine_key(keyspace, identifier) + + synchronize do + builders = @builders.dup + builders.delete(key) + @builders = builders + end + + self + end + + def default=(builder) + validate_builder(builder) + + synchronize do + @builders.default = builder + end + + builder + end + + private + + def determine_key(keyspace, identifier) + "#{keyspace}.#{identifier}" + end + + def validate_builder(builder) + unless valid_builder?(builder) + raise ::ArgumentError, + "#{builder} is not a valid builder class. " \ + 'Builders must respond to .new, #[]=, and #build' + end + end + + def valid_builder?(builder) + builder.respond_to?(:new) && + builder.method_defined?(:[]=) && + builder.method_defined?(:build) + end + end + end + end +end diff --git a/lib/cassandra/protocol/coder.rb b/lib/cassandra/protocol/coder.rb index ebd9d96cf..faf3f4648 100644 --- a/lib/cassandra/protocol/coder.rb +++ b/lib/cassandra/protocol/coder.rb @@ -224,13 +224,15 @@ def read_type_v4(buffer) def read_values_v4(buffer, column_metadata, custom_type_handlers) ::Array.new(buffer.read_int) do |_i| - row = ::Hash.new + metadata = column_metadata.first + builder = Builders.row_registry.fetch(metadata[0], metadata[1]) + row = builder.new column_metadata.each do |(_, _, column, type)| row[column] = read_value_v4(buffer, type, custom_type_handlers) end - row + row.build end end