cql-rb 1.0.0.pre4 → 1.0.0.pre5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +2 -3
- data/lib/cql/client.rb +65 -15
- data/lib/cql/io.rb +2 -0
- data/lib/cql/io/io_reactor.rb +90 -304
- data/lib/cql/io/node_connection.rb +206 -0
- data/lib/cql/protocol/encoding.rb +1 -0
- data/lib/cql/protocol/request_frame.rb +26 -0
- data/lib/cql/protocol/response_frame.rb +17 -0
- data/lib/cql/version.rb +1 -1
- data/spec/cql/client_spec.rb +95 -56
- data/spec/cql/io/io_reactor_spec.rb +47 -37
- data/spec/cql/protocol/encoding_spec.rb +5 -0
- data/spec/cql/protocol/request_frame_spec.rb +74 -0
- data/spec/cql/protocol/response_frame_spec.rb +18 -0
- data/spec/integration/client_spec.rb +46 -9
- data/spec/integration/protocol_spec.rb +83 -14
- data/spec/integration/regression_spec.rb +2 -2
- data/spec/spec_helper.rb +6 -0
- metadata +3 -2
| @@ -0,0 +1,206 @@ | |
| 1 | 
            +
            # encoding: utf-8
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'socket'
         | 
| 4 | 
            +
            require 'resolv-replace'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
             | 
| 7 | 
            +
            module Cql
         | 
| 8 | 
            +
              module Io
         | 
| 9 | 
            +
                # @private
         | 
| 10 | 
            +
                class NodeConnection
         | 
| 11 | 
            +
                  def initialize(*args)
         | 
| 12 | 
            +
                    @host, @port, @connection_timeout = args
         | 
| 13 | 
            +
                    @connected_future = Future.new
         | 
| 14 | 
            +
                    @io = nil
         | 
| 15 | 
            +
                    @addrinfo = nil
         | 
| 16 | 
            +
                    @write_buffer = ''
         | 
| 17 | 
            +
                    @read_buffer = ''
         | 
| 18 | 
            +
                    @current_frame = Protocol::ResponseFrame.new(@read_buffer)
         | 
| 19 | 
            +
                    @response_tasks = [nil] * 128
         | 
| 20 | 
            +
                    @event_listeners = Hash.new { |h, k| h[k] = [] }
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  def open
         | 
| 24 | 
            +
                    @connection_started_at = Time.now
         | 
| 25 | 
            +
                    begin
         | 
| 26 | 
            +
                      addrinfo = Socket.getaddrinfo(@host, @port, Socket::AF_INET, Socket::SOCK_STREAM)
         | 
| 27 | 
            +
                      _, port, _, ip, address_family, socket_type = addrinfo.first
         | 
| 28 | 
            +
                      @sockaddr = Socket.sockaddr_in(port, ip)
         | 
| 29 | 
            +
                      @io = Socket.new(address_family, socket_type, 0)
         | 
| 30 | 
            +
                      @io.connect_nonblock(@sockaddr)
         | 
| 31 | 
            +
                    rescue Errno::EINPROGRESS
         | 
| 32 | 
            +
                      # ok
         | 
| 33 | 
            +
                    rescue SystemCallError, SocketError => e
         | 
| 34 | 
            +
                      fail_connection!(e)
         | 
| 35 | 
            +
                    end
         | 
| 36 | 
            +
                    @connected_future
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  def connection_id
         | 
| 40 | 
            +
                    self.object_id
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  def to_io
         | 
| 44 | 
            +
                    @io
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                  def on_event(&listener)
         | 
| 48 | 
            +
                    @event_listeners[:event] << listener
         | 
| 49 | 
            +
                  end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  def on_close(&listener)
         | 
| 52 | 
            +
                    @event_listeners[:close] << listener
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                  def connected?
         | 
| 56 | 
            +
                    @io && !connecting?
         | 
| 57 | 
            +
                  end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                  def connecting?
         | 
| 60 | 
            +
                    @io && !(@connected_future.complete? || @connected_future.failed?)
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                  def closed?
         | 
| 64 | 
            +
                    @io.nil? && !connecting?
         | 
| 65 | 
            +
                  end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                  def has_capacity?
         | 
| 68 | 
            +
                    !!next_stream_id && connected?
         | 
| 69 | 
            +
                  end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                  def can_write?
         | 
| 72 | 
            +
                    @io && (!@write_buffer.empty? || connecting?)
         | 
| 73 | 
            +
                  end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                  def perform_request(request, future)
         | 
| 76 | 
            +
                    stream_id = next_stream_id
         | 
| 77 | 
            +
                    Protocol::RequestFrame.new(request, stream_id).write(@write_buffer)
         | 
| 78 | 
            +
                    @response_tasks[stream_id] = future
         | 
| 79 | 
            +
                  rescue => e
         | 
| 80 | 
            +
                    case e
         | 
| 81 | 
            +
                    when CqlError
         | 
| 82 | 
            +
                      error = e
         | 
| 83 | 
            +
                    else
         | 
| 84 | 
            +
                      error = IoError.new(e.message)
         | 
| 85 | 
            +
                      error.set_backtrace(e.backtrace)
         | 
| 86 | 
            +
                    end
         | 
| 87 | 
            +
                    @response_tasks.delete(stream_id)
         | 
| 88 | 
            +
                    future.fail!(error)
         | 
| 89 | 
            +
                  end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                  def handle_read
         | 
| 92 | 
            +
                    new_bytes = @io.read_nonblock(2**16)
         | 
| 93 | 
            +
                    @current_frame << new_bytes
         | 
| 94 | 
            +
                    while @current_frame.complete?
         | 
| 95 | 
            +
                      stream_id = @current_frame.stream_id
         | 
| 96 | 
            +
                      if stream_id == EVENT_STREAM_ID
         | 
| 97 | 
            +
                        @event_listeners[:event].each { |listener| listener.call(@current_frame.body) }
         | 
| 98 | 
            +
                      elsif @response_tasks[stream_id]
         | 
| 99 | 
            +
                        @response_tasks[stream_id].complete!([@current_frame.body, connection_id])
         | 
| 100 | 
            +
                        @response_tasks[stream_id] = nil
         | 
| 101 | 
            +
                      else
         | 
| 102 | 
            +
                        # TODO dropping the request on the floor here, but we didn't send it
         | 
| 103 | 
            +
                      end
         | 
| 104 | 
            +
                      @current_frame = Protocol::ResponseFrame.new(@read_buffer)
         | 
| 105 | 
            +
                    end
         | 
| 106 | 
            +
                  rescue => e
         | 
| 107 | 
            +
                    force_close(e)
         | 
| 108 | 
            +
                  end
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                  def handle_write
         | 
| 111 | 
            +
                    succeed_connection! if connecting?
         | 
| 112 | 
            +
                    if !@write_buffer.empty?
         | 
| 113 | 
            +
                      bytes_written = @io.write_nonblock(@write_buffer)
         | 
| 114 | 
            +
                      @write_buffer.slice!(0, bytes_written)
         | 
| 115 | 
            +
                    end
         | 
| 116 | 
            +
                  rescue => e
         | 
| 117 | 
            +
                    force_close(e)
         | 
| 118 | 
            +
                  end
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                  def handle_connecting
         | 
| 121 | 
            +
                    if connecting_timed_out?
         | 
| 122 | 
            +
                      fail_connection!(ConnectionTimeoutError.new("Could not connect to #{@host}:#{@port} within #{@connection_timeout}s"))
         | 
| 123 | 
            +
                    else
         | 
| 124 | 
            +
                      @io.connect_nonblock(@sockaddr)
         | 
| 125 | 
            +
                      succeed_connection!
         | 
| 126 | 
            +
                    end
         | 
| 127 | 
            +
                  rescue Errno::EISCONN
         | 
| 128 | 
            +
                    # ok
         | 
| 129 | 
            +
                    succeed_connection!
         | 
| 130 | 
            +
                  rescue SystemCallError, SocketError => e
         | 
| 131 | 
            +
                    fail_connection!(e)
         | 
| 132 | 
            +
                  end
         | 
| 133 | 
            +
             | 
| 134 | 
            +
                  def close
         | 
| 135 | 
            +
                    if @io
         | 
| 136 | 
            +
                      begin
         | 
| 137 | 
            +
                        @io.close
         | 
| 138 | 
            +
                      rescue SystemCallError
         | 
| 139 | 
            +
                        # nothing to do, it wasn't open
         | 
| 140 | 
            +
                      end
         | 
| 141 | 
            +
                      if connecting?
         | 
| 142 | 
            +
                        succeed_connection!
         | 
| 143 | 
            +
                      end
         | 
| 144 | 
            +
                      @io = nil
         | 
| 145 | 
            +
                      @event_listeners[:close].each { |listener| listener.call(self) }
         | 
| 146 | 
            +
                    end
         | 
| 147 | 
            +
                  end
         | 
| 148 | 
            +
             | 
| 149 | 
            +
                  def to_s
         | 
| 150 | 
            +
                    state = begin
         | 
| 151 | 
            +
                      if connected? then 'connected'
         | 
| 152 | 
            +
                      elsif connecting? then 'connecting'
         | 
| 153 | 
            +
                      else 'not connected'
         | 
| 154 | 
            +
                      end
         | 
| 155 | 
            +
                    end
         | 
| 156 | 
            +
                    %<NodeConnection(#{@host}:#{@port}, #{state})>
         | 
| 157 | 
            +
                  end
         | 
| 158 | 
            +
             | 
| 159 | 
            +
                  private
         | 
| 160 | 
            +
             | 
| 161 | 
            +
                  EVENT_STREAM_ID = -1
         | 
| 162 | 
            +
             | 
| 163 | 
            +
                  def connecting_timed_out?
         | 
| 164 | 
            +
                    (Time.now - @connection_started_at) > @connection_timeout
         | 
| 165 | 
            +
                  end
         | 
| 166 | 
            +
             | 
| 167 | 
            +
                  def succeed_connection!
         | 
| 168 | 
            +
                    @connected_future.complete!(connection_id)
         | 
| 169 | 
            +
                  end
         | 
| 170 | 
            +
             | 
| 171 | 
            +
                  def fail_connection!(e)
         | 
| 172 | 
            +
                    case e
         | 
| 173 | 
            +
                    when ConnectionError
         | 
| 174 | 
            +
                      error = e
         | 
| 175 | 
            +
                    else
         | 
| 176 | 
            +
                      message = "Could not connect to #{@host}:#{@port}: #{e.message} (#{e.class.name})"
         | 
| 177 | 
            +
                      error = ConnectionError.new(message)
         | 
| 178 | 
            +
                      error.set_backtrace(e.backtrace)
         | 
| 179 | 
            +
                    end
         | 
| 180 | 
            +
                    @connected_future.fail!(error)
         | 
| 181 | 
            +
                    force_close(error)
         | 
| 182 | 
            +
                  end
         | 
| 183 | 
            +
             | 
| 184 | 
            +
                  def force_close(e)
         | 
| 185 | 
            +
                    case e
         | 
| 186 | 
            +
                    when CqlError
         | 
| 187 | 
            +
                      error = e
         | 
| 188 | 
            +
                    else
         | 
| 189 | 
            +
                      error = IoError.new(e.message)
         | 
| 190 | 
            +
                      error.set_backtrace(e.backtrace)
         | 
| 191 | 
            +
                    end
         | 
| 192 | 
            +
                    @response_tasks.each do |listener|
         | 
| 193 | 
            +
                      listener.fail!(error) if listener
         | 
| 194 | 
            +
                    end
         | 
| 195 | 
            +
                    close
         | 
| 196 | 
            +
                  end
         | 
| 197 | 
            +
             | 
| 198 | 
            +
                  def next_stream_id
         | 
| 199 | 
            +
                    @response_tasks.each_with_index do |task, index|
         | 
| 200 | 
            +
                      return index if task.nil?
         | 
| 201 | 
            +
                    end
         | 
| 202 | 
            +
                    nil
         | 
| 203 | 
            +
                  end
         | 
| 204 | 
            +
                end
         | 
| 205 | 
            +
              end
         | 
| 206 | 
            +
            end
         | 
| @@ -49,6 +49,32 @@ module Cql | |
| 49 49 | 
             
                  COMPRESSION = 'COMPRESSION'.freeze
         | 
| 50 50 | 
             
                end
         | 
| 51 51 |  | 
| 52 | 
            +
                class CredentialsRequest < RequestBody
         | 
| 53 | 
            +
                  attr_reader :credentials
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                  def initialize(credentials)
         | 
| 56 | 
            +
                    super(4)
         | 
| 57 | 
            +
                    @credentials = credentials.dup.freeze
         | 
| 58 | 
            +
                  end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                  def write(io)
         | 
| 61 | 
            +
                    write_string_map(io, @credentials)
         | 
| 62 | 
            +
                  end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                  def to_s
         | 
| 65 | 
            +
                    %(CREDENTIALS #{@credentials})
         | 
| 66 | 
            +
                  end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                  def eql?(rq)
         | 
| 69 | 
            +
                    self.class === rq && rq.credentials.eql?(@credentials)
         | 
| 70 | 
            +
                  end
         | 
| 71 | 
            +
                  alias_method :==, :eql?
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                  def hash
         | 
| 74 | 
            +
                    @h ||= @credentials.hash
         | 
| 75 | 
            +
                  end
         | 
| 76 | 
            +
                end
         | 
| 77 | 
            +
             | 
| 52 78 | 
             
                class OptionsRequest < RequestBody
         | 
| 53 79 | 
             
                  def initialize
         | 
| 54 80 | 
             
                    super(5)
         | 
| @@ -55,6 +55,7 @@ module Cql | |
| 55 55 | 
             
                      case @headers.opcode
         | 
| 56 56 | 
             
                      when 0x00 then ErrorResponse
         | 
| 57 57 | 
             
                      when 0x02 then ReadyResponse
         | 
| 58 | 
            +
                      when 0x03 then AuthenticateResponse
         | 
| 58 59 | 
             
                      when 0x06 then SupportedResponse
         | 
| 59 60 | 
             
                      when 0x08 then ResultResponse
         | 
| 60 61 | 
             
                      when 0x0c then EventResponse
         | 
| @@ -214,6 +215,22 @@ module Cql | |
| 214 215 | 
             
                  end
         | 
| 215 216 | 
             
                end
         | 
| 216 217 |  | 
| 218 | 
            +
                class AuthenticateResponse < ResponseBody
         | 
| 219 | 
            +
                  attr_reader :authentication_class
         | 
| 220 | 
            +
             | 
| 221 | 
            +
                  def self.decode!(buffer)
         | 
| 222 | 
            +
                    new(read_string!(buffer))
         | 
| 223 | 
            +
                  end
         | 
| 224 | 
            +
             | 
| 225 | 
            +
                  def initialize(authentication_class)
         | 
| 226 | 
            +
                    @authentication_class = authentication_class
         | 
| 227 | 
            +
                  end
         | 
| 228 | 
            +
             | 
| 229 | 
            +
                  def to_s
         | 
| 230 | 
            +
                    %(AUTHENTICATE #{authentication_class})
         | 
| 231 | 
            +
                  end
         | 
| 232 | 
            +
                end
         | 
| 233 | 
            +
             | 
| 217 234 | 
             
                class SupportedResponse < ResponseBody
         | 
| 218 235 | 
             
                  attr_reader :options
         | 
| 219 236 |  | 
    
        data/lib/cql/version.rb
    CHANGED
    
    
    
        data/spec/cql/client_spec.rb
    CHANGED
    
    | @@ -33,114 +33,153 @@ module Cql | |
| 33 33 | 
             
                  requests.last
         | 
| 34 34 | 
             
                end
         | 
| 35 35 |  | 
| 36 | 
            -
                describe ' | 
| 36 | 
            +
                describe '.connect' do
         | 
| 37 | 
            +
                  it 'connects and returns the client' do
         | 
| 38 | 
            +
                    client = described_class.connect(connection_options)
         | 
| 39 | 
            +
                    client.should be_connected
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                describe '#connect' do
         | 
| 37 44 | 
             
                  it 'connects' do
         | 
| 38 | 
            -
                    client. | 
| 45 | 
            +
                    client.connect
         | 
| 39 46 | 
             
                    connections.should have(1).item
         | 
| 40 47 | 
             
                  end
         | 
| 41 48 |  | 
| 42 49 | 
             
                  it 'connects only once' do
         | 
| 43 | 
            -
                    client. | 
| 44 | 
            -
                    client. | 
| 50 | 
            +
                    client.connect
         | 
| 51 | 
            +
                    client.connect
         | 
| 45 52 | 
             
                    connections.should have(1).item
         | 
| 46 53 | 
             
                  end
         | 
| 47 54 |  | 
| 48 55 | 
             
                  it 'connects to all hosts' do
         | 
| 49 | 
            -
                    client. | 
| 56 | 
            +
                    client.close
         | 
| 50 57 | 
             
                    io_reactor.stop.get
         | 
| 51 58 | 
             
                    io_reactor.start.get
         | 
| 52 59 |  | 
| 53 60 | 
             
                    c = described_class.new(connection_options.merge(host: 'h1.example.com,h2.example.com,h3.example.com'))
         | 
| 54 | 
            -
                    c. | 
| 61 | 
            +
                    c.connect
         | 
| 55 62 | 
             
                    connections.should have(3).items
         | 
| 56 63 | 
             
                  end
         | 
| 57 64 |  | 
| 58 65 | 
             
                  it 'returns itself' do
         | 
| 59 | 
            -
                    client. | 
| 66 | 
            +
                    client.connect.should equal(client)
         | 
| 60 67 | 
             
                  end
         | 
| 61 68 |  | 
| 62 69 | 
             
                  it 'forwards the host and port' do
         | 
| 63 | 
            -
                    client. | 
| 70 | 
            +
                    client.connect
         | 
| 64 71 | 
             
                    connection[:host].should == 'example.com'
         | 
| 65 72 | 
             
                    connection[:port].should == 12321
         | 
| 66 73 | 
             
                  end
         | 
| 67 74 |  | 
| 68 75 | 
             
                  it 'sends a startup request' do
         | 
| 69 | 
            -
                    client. | 
| 76 | 
            +
                    client.connect
         | 
| 70 77 | 
             
                    last_request.should be_a(Protocol::StartupRequest)
         | 
| 71 78 | 
             
                  end
         | 
| 72 79 |  | 
| 73 80 | 
             
                  it 'sends a startup request to each connection' do
         | 
| 74 | 
            -
                    client. | 
| 81 | 
            +
                    client.close
         | 
| 75 82 | 
             
                    io_reactor.stop.get
         | 
| 76 83 | 
             
                    io_reactor.start.get
         | 
| 77 84 |  | 
| 78 85 | 
             
                    c = described_class.new(connection_options.merge(host: 'h1.example.com,h2.example.com,h3.example.com'))
         | 
| 79 | 
            -
                    c. | 
| 86 | 
            +
                    c.connect
         | 
| 80 87 | 
             
                    connections.each do |cc|
         | 
| 81 88 | 
             
                      cc[:requests].last.should be_a(Protocol::StartupRequest)
         | 
| 82 89 | 
             
                    end
         | 
| 83 90 | 
             
                  end
         | 
| 84 91 |  | 
| 85 92 | 
             
                  it 'is not in a keyspace' do
         | 
| 86 | 
            -
                    client. | 
| 93 | 
            +
                    client.connect
         | 
| 87 94 | 
             
                    client.keyspace.should be_nil
         | 
| 88 95 | 
             
                  end
         | 
| 89 96 |  | 
| 90 97 | 
             
                  it 'changes to the keyspace given as an option' do
         | 
| 91 98 | 
             
                    c = described_class.new(connection_options.merge(:keyspace => 'hello_world'))
         | 
| 92 | 
            -
                    c. | 
| 99 | 
            +
                    c.connect
         | 
| 93 100 | 
             
                    last_request.should == Protocol::QueryRequest.new('USE hello_world', :one)
         | 
| 94 101 | 
             
                  end
         | 
| 95 102 |  | 
| 96 103 | 
             
                  it 'validates the keyspace name before sending the USE command' do
         | 
| 97 104 | 
             
                    c = described_class.new(connection_options.merge(:keyspace => 'system; DROP KEYSPACE system'))
         | 
| 98 | 
            -
                    expect { c. | 
| 105 | 
            +
                    expect { c.connect }.to raise_error(Client::InvalidKeyspaceNameError)
         | 
| 99 106 | 
             
                    requests.should_not include(Protocol::QueryRequest.new('USE system; DROP KEYSPACE system', :one))
         | 
| 100 107 | 
             
                  end
         | 
| 101 108 |  | 
| 102 109 | 
             
                  it 're-raises any errors raised' do
         | 
| 103 110 | 
             
                    io_reactor.stub(:add_connection).and_raise(ArgumentError)
         | 
| 104 | 
            -
                    expect { client. | 
| 111 | 
            +
                    expect { client.connect }.to raise_error(ArgumentError)
         | 
| 105 112 | 
             
                  end
         | 
| 106 113 |  | 
| 107 114 | 
             
                  it 'is not connected if an error is raised' do
         | 
| 108 115 | 
             
                    io_reactor.stub(:add_connection).and_raise(ArgumentError)
         | 
| 109 | 
            -
                    client. | 
| 116 | 
            +
                    client.connect rescue nil
         | 
| 110 117 | 
             
                    client.should_not be_connected
         | 
| 118 | 
            +
                    io_reactor.should_not be_running
         | 
| 111 119 | 
             
                  end
         | 
| 112 120 |  | 
| 113 | 
            -
                  it 'is connected after # | 
| 114 | 
            -
                    client. | 
| 121 | 
            +
                  it 'is connected after #connect returns' do
         | 
| 122 | 
            +
                    client.connect
         | 
| 115 123 | 
             
                    client.should be_connected
         | 
| 116 124 | 
             
                  end
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                  context 'when the server requests authentication' do
         | 
| 127 | 
            +
                    before do
         | 
| 128 | 
            +
                      io_reactor.queue_response(Protocol::AuthenticateResponse.new('com.example.Auth'))
         | 
| 129 | 
            +
                    end
         | 
| 130 | 
            +
             | 
| 131 | 
            +
                    it 'sends credentials' do
         | 
| 132 | 
            +
                      client = described_class.new(connection_options.merge(credentials: {'username' => 'foo', 'password' => 'bar'}))
         | 
| 133 | 
            +
                      client.connect
         | 
| 134 | 
            +
                      last_request.should == Protocol::CredentialsRequest.new('username' => 'foo', 'password' => 'bar')
         | 
| 135 | 
            +
                    end
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                    it 'raises an error when no credentials have been given' do
         | 
| 138 | 
            +
                      client = described_class.new(connection_options)
         | 
| 139 | 
            +
                      expect { client.connect }.to raise_error(AuthenticationError)
         | 
| 140 | 
            +
                    end
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                    it 'raises an error when the server responds with an error to the credentials request' do
         | 
| 143 | 
            +
                      io_reactor.queue_response(Protocol::ErrorResponse.new(256, 'No way, José'))
         | 
| 144 | 
            +
                      client = described_class.new(connection_options.merge(credentials: {'username' => 'foo', 'password' => 'bar'}))
         | 
| 145 | 
            +
                      expect { client.connect }.to raise_error(AuthenticationError)
         | 
| 146 | 
            +
                    end
         | 
| 147 | 
            +
             | 
| 148 | 
            +
                    it 'shuts down the client when there is an authentication error' do
         | 
| 149 | 
            +
                      io_reactor.queue_response(Protocol::ErrorResponse.new(256, 'No way, José'))
         | 
| 150 | 
            +
                      client = described_class.new(connection_options.merge(credentials: {'username' => 'foo', 'password' => 'bar'}))
         | 
| 151 | 
            +
                      client.connect rescue nil
         | 
| 152 | 
            +
                      client.should_not be_connected
         | 
| 153 | 
            +
                      io_reactor.should_not be_running
         | 
| 154 | 
            +
                    end
         | 
| 155 | 
            +
                  end
         | 
| 117 156 | 
             
                end
         | 
| 118 157 |  | 
| 119 | 
            -
                describe '# | 
| 158 | 
            +
                describe '#close' do
         | 
| 120 159 | 
             
                  it 'closes the connection' do
         | 
| 121 | 
            -
                    client. | 
| 122 | 
            -
                    client. | 
| 160 | 
            +
                    client.connect
         | 
| 161 | 
            +
                    client.close
         | 
| 123 162 | 
             
                    io_reactor.should_not be_running
         | 
| 124 163 | 
             
                  end
         | 
| 125 164 |  | 
| 126 | 
            -
                  it 'does nothing when called before # | 
| 127 | 
            -
                    client. | 
| 165 | 
            +
                  it 'does nothing when called before #connect' do
         | 
| 166 | 
            +
                    client.close
         | 
| 128 167 | 
             
                  end
         | 
| 129 168 |  | 
| 130 | 
            -
                  it 'accepts multiple calls to # | 
| 131 | 
            -
                    client. | 
| 132 | 
            -
                    client. | 
| 133 | 
            -
                    client. | 
| 169 | 
            +
                  it 'accepts multiple calls to #close' do
         | 
| 170 | 
            +
                    client.connect
         | 
| 171 | 
            +
                    client.close
         | 
| 172 | 
            +
                    client.close
         | 
| 134 173 | 
             
                  end
         | 
| 135 174 |  | 
| 136 175 | 
             
                  it 'returns itself' do
         | 
| 137 | 
            -
                    client. | 
| 176 | 
            +
                    client.connect.close.should equal(client)
         | 
| 138 177 | 
             
                  end
         | 
| 139 178 | 
             
                end
         | 
| 140 179 |  | 
| 141 180 | 
             
                describe '#use' do
         | 
| 142 181 | 
             
                  before do
         | 
| 143 | 
            -
                    client. | 
| 182 | 
            +
                    client.connect
         | 
| 144 183 | 
             
                  end
         | 
| 145 184 |  | 
| 146 185 | 
             
                  it 'executes a USE query' do
         | 
| @@ -150,12 +189,12 @@ module Cql | |
| 150 189 | 
             
                  end
         | 
| 151 190 |  | 
| 152 191 | 
             
                  it 'executes a USE query for each connection' do
         | 
| 153 | 
            -
                    client. | 
| 192 | 
            +
                    client.close
         | 
| 154 193 | 
             
                    io_reactor.stop.get
         | 
| 155 194 | 
             
                    io_reactor.start.get
         | 
| 156 195 |  | 
| 157 196 | 
             
                    c = described_class.new(connection_options.merge(host: 'h1.example.com,h2.example.com,h3.example.com'))
         | 
| 158 | 
            -
                    c. | 
| 197 | 
            +
                    c.connect
         | 
| 159 198 |  | 
| 160 199 | 
             
                    c.use('system')
         | 
| 161 200 | 
             
                    last_requests = connections.select { |c| c[:host] =~ /^h\d\.example\.com$/ }.sort_by { |c| c[:host] }.map { |c| c[:requests].last }
         | 
| @@ -179,7 +218,7 @@ module Cql | |
| 179 218 |  | 
| 180 219 | 
             
                describe '#execute' do
         | 
| 181 220 | 
             
                  before do
         | 
| 182 | 
            -
                    client. | 
| 221 | 
            +
                    client.connect
         | 
| 183 222 | 
             
                  end
         | 
| 184 223 |  | 
| 185 224 | 
             
                  it 'asks the connection to execute the query' do
         | 
| @@ -214,12 +253,12 @@ module Cql | |
| 214 253 | 
             
                    end
         | 
| 215 254 |  | 
| 216 255 | 
             
                    it 'detects that one connection changed to a keyspace and changes the others too' do
         | 
| 217 | 
            -
                      client. | 
| 256 | 
            +
                      client.close
         | 
| 218 257 | 
             
                      io_reactor.stop.get
         | 
| 219 258 | 
             
                      io_reactor.start.get
         | 
| 220 259 |  | 
| 221 260 | 
             
                      c = described_class.new(connection_options.merge(host: 'h1.example.com,h2.example.com,h3.example.com'))
         | 
| 222 | 
            -
                      c. | 
| 261 | 
            +
                      c.connect
         | 
| 223 262 |  | 
| 224 263 | 
             
                      io_reactor.queue_response(Protocol::SetKeyspaceResultResponse.new('system'), connections.find { |c| c[:host] == 'h1.example.com' }[:host])
         | 
| 225 264 | 
             
                      io_reactor.queue_response(Protocol::SetKeyspaceResultResponse.new('system'), connections.find { |c| c[:host] == 'h2.example.com' }[:host])
         | 
| @@ -299,7 +338,7 @@ module Cql | |
| 299 338 | 
             
                  end
         | 
| 300 339 |  | 
| 301 340 | 
             
                  before do
         | 
| 302 | 
            -
                    client. | 
| 341 | 
            +
                    client.connect
         | 
| 303 342 | 
             
                  end
         | 
| 304 343 |  | 
| 305 344 | 
             
                  it 'sends a prepare request' do
         | 
| @@ -334,12 +373,12 @@ module Cql | |
| 334 373 | 
             
                  end
         | 
| 335 374 |  | 
| 336 375 | 
             
                  it 'executes a prepared statement using the right connection' do
         | 
| 337 | 
            -
                    client. | 
| 376 | 
            +
                    client.close
         | 
| 338 377 | 
             
                    io_reactor.stop.get
         | 
| 339 378 | 
             
                    io_reactor.start.get
         | 
| 340 379 |  | 
| 341 380 | 
             
                    c = described_class.new(connection_options.merge(host: 'h1.example.com,h2.example.com,h3.example.com'))
         | 
| 342 | 
            -
                    c. | 
| 381 | 
            +
                    c.connect
         | 
| 343 382 |  | 
| 344 383 | 
             
                    io_reactor.queue_response(Protocol::PreparedResultResponse.new('A' * 32, metadata))
         | 
| 345 384 | 
             
                    io_reactor.queue_response(Protocol::PreparedResultResponse.new('B' * 32, metadata))
         | 
| @@ -363,51 +402,51 @@ module Cql | |
| 363 402 | 
             
                end
         | 
| 364 403 |  | 
| 365 404 | 
             
                context 'when not connected' do
         | 
| 366 | 
            -
                  it 'is not connected before # | 
| 405 | 
            +
                  it 'is not connected before #connect has been called' do
         | 
| 367 406 | 
             
                    client.should_not be_connected
         | 
| 368 407 | 
             
                  end
         | 
| 369 408 |  | 
| 370 | 
            -
                  it 'is not connected after # | 
| 371 | 
            -
                    client. | 
| 372 | 
            -
                    client. | 
| 409 | 
            +
                  it 'is not connected after #close has been called' do
         | 
| 410 | 
            +
                    client.connect
         | 
| 411 | 
            +
                    client.close
         | 
| 373 412 | 
             
                    client.should_not be_connected
         | 
| 374 413 | 
             
                  end
         | 
| 375 414 |  | 
| 376 | 
            -
                  it 'complains when #use is called before # | 
| 415 | 
            +
                  it 'complains when #use is called before #connect' do
         | 
| 377 416 | 
             
                    expect { client.use('system') }.to raise_error(Client::NotConnectedError)
         | 
| 378 417 | 
             
                  end
         | 
| 379 418 |  | 
| 380 | 
            -
                  it 'complains when #use is called after # | 
| 381 | 
            -
                    client. | 
| 382 | 
            -
                    client. | 
| 419 | 
            +
                  it 'complains when #use is called after #close' do
         | 
| 420 | 
            +
                    client.connect
         | 
| 421 | 
            +
                    client.close
         | 
| 383 422 | 
             
                    expect { client.use('system') }.to raise_error(Client::NotConnectedError)
         | 
| 384 423 | 
             
                  end
         | 
| 385 424 |  | 
| 386 | 
            -
                  it 'complains when #execute is called before # | 
| 425 | 
            +
                  it 'complains when #execute is called before #connect' do
         | 
| 387 426 | 
             
                    expect { client.execute('DELETE FROM stuff WHERE id = 3') }.to raise_error(Client::NotConnectedError)
         | 
| 388 427 | 
             
                  end
         | 
| 389 428 |  | 
| 390 | 
            -
                  it 'complains when #execute is called after # | 
| 391 | 
            -
                    client. | 
| 392 | 
            -
                    client. | 
| 429 | 
            +
                  it 'complains when #execute is called after #close' do
         | 
| 430 | 
            +
                    client.connect
         | 
| 431 | 
            +
                    client.close
         | 
| 393 432 | 
             
                    expect { client.execute('DELETE FROM stuff WHERE id = 3') }.to raise_error(Client::NotConnectedError)
         | 
| 394 433 | 
             
                  end
         | 
| 395 434 |  | 
| 396 | 
            -
                  it 'complains when #prepare is called before # | 
| 435 | 
            +
                  it 'complains when #prepare is called before #connect' do
         | 
| 397 436 | 
             
                    expect { client.prepare('DELETE FROM stuff WHERE id = 3') }.to raise_error(Client::NotConnectedError)
         | 
| 398 437 | 
             
                  end
         | 
| 399 438 |  | 
| 400 | 
            -
                  it 'complains when #prepare is called after # | 
| 401 | 
            -
                    client. | 
| 402 | 
            -
                    client. | 
| 439 | 
            +
                  it 'complains when #prepare is called after #close' do
         | 
| 440 | 
            +
                    client.connect
         | 
| 441 | 
            +
                    client.close
         | 
| 403 442 | 
             
                    expect { client.prepare('DELETE FROM stuff WHERE id = 3') }.to raise_error(Client::NotConnectedError)
         | 
| 404 443 | 
             
                  end
         | 
| 405 444 |  | 
| 406 | 
            -
                  it 'complains when #execute of a prepared statement is called after # | 
| 407 | 
            -
                    client. | 
| 445 | 
            +
                  it 'complains when #execute of a prepared statement is called after #close' do
         | 
| 446 | 
            +
                    client.connect
         | 
| 408 447 | 
             
                    io_reactor.queue_response(Protocol::PreparedResultResponse.new('A' * 32, []))
         | 
| 409 448 | 
             
                    statement = client.prepare('DELETE FROM stuff WHERE id = 3')
         | 
| 410 | 
            -
                    client. | 
| 449 | 
            +
                    client.close
         | 
| 411 450 | 
             
                    expect { statement.execute }.to raise_error(Client::NotConnectedError)
         | 
| 412 451 | 
             
                  end
         | 
| 413 452 | 
             
                end
         |