cassandra-driver 2.1.7 → 3.0.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +8 -8
  2. data/README.md +31 -53
  3. data/lib/cassandra.rb +22 -3
  4. data/lib/cassandra/aggregate.rb +109 -0
  5. data/lib/cassandra/argument.rb +51 -0
  6. data/lib/cassandra/auth/providers/password.rb +7 -4
  7. data/lib/cassandra/cluster.rb +14 -3
  8. data/lib/cassandra/cluster/client.rb +56 -34
  9. data/lib/cassandra/cluster/connector.rb +6 -6
  10. data/lib/cassandra/cluster/control_connection.rb +204 -251
  11. data/lib/cassandra/cluster/metadata.rb +2 -0
  12. data/lib/cassandra/cluster/schema.rb +131 -209
  13. data/lib/cassandra/cluster/schema/cql_type_parser.rb +104 -0
  14. data/lib/cassandra/cluster/schema/fetchers.rb +1174 -0
  15. data/lib/cassandra/cluster/schema/{type_parser.rb → fqcn_type_parser.rb} +7 -3
  16. data/lib/cassandra/column.rb +2 -2
  17. data/lib/cassandra/driver.rb +27 -9
  18. data/lib/cassandra/errors.rb +179 -25
  19. data/lib/cassandra/execution/info.rb +8 -1
  20. data/lib/cassandra/execution/options.rb +34 -0
  21. data/lib/cassandra/execution/trace.rb +42 -10
  22. data/lib/cassandra/function.rb +150 -0
  23. data/lib/cassandra/future.rb +66 -35
  24. data/lib/cassandra/host.rb +7 -4
  25. data/lib/cassandra/keyspace.rb +112 -13
  26. data/lib/cassandra/load_balancing.rb +1 -1
  27. data/lib/cassandra/protocol.rb +9 -3
  28. data/lib/cassandra/protocol/coder.rb +434 -155
  29. data/lib/cassandra/protocol/cql_byte_buffer.rb +43 -0
  30. data/lib/cassandra/protocol/cql_protocol_handler.rb +4 -1
  31. data/lib/cassandra/protocol/request.rb +4 -0
  32. data/lib/cassandra/protocol/requests/auth_response_request.rb +5 -1
  33. data/lib/cassandra/protocol/requests/batch_request.rb +7 -2
  34. data/lib/cassandra/protocol/requests/credentials_request.rb +5 -1
  35. data/lib/cassandra/protocol/requests/execute_request.rb +16 -10
  36. data/lib/cassandra/protocol/requests/prepare_request.rb +12 -3
  37. data/lib/cassandra/protocol/requests/query_request.rb +20 -11
  38. data/lib/cassandra/protocol/responses/already_exists_error_response.rb +4 -4
  39. data/lib/cassandra/protocol/responses/error_response.rb +14 -14
  40. data/lib/cassandra/protocol/responses/function_failure_error_response.rb +41 -0
  41. data/lib/cassandra/protocol/responses/prepared_result_response.rb +12 -9
  42. data/lib/cassandra/protocol/responses/raw_rows_result_response.rb +5 -3
  43. data/lib/cassandra/protocol/responses/read_failure_error_response.rb +43 -0
  44. data/lib/cassandra/protocol/responses/read_timeout_error_response.rb +4 -4
  45. data/lib/cassandra/protocol/responses/ready_response.rb +5 -1
  46. data/lib/cassandra/protocol/responses/result_response.rb +3 -3
  47. data/lib/cassandra/protocol/responses/rows_result_response.rb +2 -2
  48. data/lib/cassandra/protocol/responses/schema_change_event_response.rb +25 -24
  49. data/lib/cassandra/protocol/responses/schema_change_result_response.rb +20 -23
  50. data/lib/cassandra/protocol/responses/set_keyspace_result_response.rb +2 -2
  51. data/lib/cassandra/protocol/responses/unavailable_error_response.rb +4 -4
  52. data/lib/cassandra/protocol/responses/unprepared_error_response.rb +4 -4
  53. data/lib/cassandra/protocol/responses/write_failure_error_response.rb +45 -0
  54. data/lib/cassandra/protocol/responses/write_timeout_error_response.rb +4 -4
  55. data/lib/cassandra/protocol/v1.rb +38 -13
  56. data/lib/cassandra/protocol/v3.rb +34 -29
  57. data/lib/cassandra/protocol/v4.rb +334 -0
  58. data/lib/cassandra/result.rb +10 -9
  59. data/lib/cassandra/retry.rb +17 -3
  60. data/lib/cassandra/retry/policies/default.rb +9 -3
  61. data/lib/cassandra/session.rb +15 -7
  62. data/lib/cassandra/statement.rb +5 -0
  63. data/lib/cassandra/statements/batch.rb +36 -12
  64. data/lib/cassandra/statements/bound.rb +2 -1
  65. data/lib/cassandra/statements/prepared.rb +106 -35
  66. data/lib/cassandra/statements/simple.rb +4 -2
  67. data/lib/cassandra/table.rb +70 -105
  68. data/lib/cassandra/time.rb +98 -0
  69. data/lib/cassandra/time_uuid.rb +1 -1
  70. data/lib/cassandra/tuple.rb +7 -0
  71. data/lib/cassandra/types.rb +472 -272
  72. data/lib/cassandra/udt.rb +10 -0
  73. data/lib/cassandra/util.rb +32 -1
  74. data/lib/cassandra/uuid.rb +6 -1
  75. data/lib/cassandra/uuid/generator.rb +7 -7
  76. data/lib/cassandra/version.rb +1 -1
  77. data/lib/datastax/cassandra.rb +5 -2
  78. metadata +16 -6
@@ -49,6 +49,19 @@ module Cassandra
49
49
  # @see Cassandra::Result#paging_state
50
50
  attr_reader :paging_state
51
51
 
52
+ # @return [nil, Hash<String, String>] custom outgoing payload, a map of
53
+ # string and byte buffers.
54
+ #
55
+ # @see https://github.com/apache/cassandra/blob/33f1edcce97779c971d4f78712a9a8bf014ffbbc/doc/native_protocol_v4.spec#L127-L133 Description of custom payload in Cassandra native protocol v4.
56
+ # @see https://datastax.github.io/java-driver/features/custom_payloads/#enabling-custom-payloads-on-c-nodes Enabling custom payloads on Cassandra nodes.
57
+ #
58
+ # @example Sending a custom payload
59
+ # result = session.execute(payload: {
60
+ # 'some key' => Cassandra::Protocol::CqlByteBuffer.new
61
+ # .append_string('some value')
62
+ # })
63
+ attr_reader :payload
64
+
52
65
  # @private
53
66
  def initialize(options)
54
67
  consistency = options[:consistency]
@@ -59,6 +72,8 @@ module Cassandra
59
72
  paging_state = options[:paging_state]
60
73
  arguments = options[:arguments]
61
74
  type_hints = options[:type_hints]
75
+ idempotent = options[:idempotent]
76
+ payload = options[:payload]
62
77
 
63
78
  Util.assert_one_of(CONSISTENCIES, consistency) { ":consistency must be one of #{CONSISTENCIES.inspect}, #{consistency.inspect} given" }
64
79
 
@@ -94,6 +109,17 @@ module Cassandra
94
109
  Util.assert_instance_of_one_of([::Array, ::Hash], type_hints) { ":type_hints must be an Array or a Hash, #{type_hints.inspect} given" }
95
110
  end
96
111
 
112
+ unless payload.nil?
113
+ Util.assert_instance_of(::Hash, payload) { ":payload must be a Hash" }
114
+ Util.assert_not_empty(payload) { ":payload must not be empty" }
115
+ Util.assert(payload.size <= 65535) { ":payload cannot contain more than 65535 key/value pairs" }
116
+
117
+ payload = payload.each_with_object(::Hash.new) do |(key, value), payload|
118
+ payload[String(key)] = String(value)
119
+ end
120
+ payload.freeze
121
+ end
122
+
97
123
  @consistency = consistency
98
124
  @page_size = page_size
99
125
  @trace = !!trace
@@ -102,6 +128,8 @@ module Cassandra
102
128
  @paging_state = paging_state
103
129
  @arguments = arguments
104
130
  @type_hints = type_hints
131
+ @idempotent = !!idempotent
132
+ @payload = payload
105
133
  end
106
134
 
107
135
  # @return [Boolean] whether request tracing was enabled
@@ -109,6 +137,12 @@ module Cassandra
109
137
  @trace
110
138
  end
111
139
 
140
+ # @return [Boolean] whether statement can be retried on timeout
141
+ def idempotent?
142
+ @idempotent
143
+ end
144
+
145
+ # @private
112
146
  def eql?(other)
113
147
  other.is_a?(Options) &&
114
148
  other.consistency == @consistency &&
@@ -39,6 +39,7 @@ module Cassandra
39
39
  @thread = thread
40
40
  end
41
41
 
42
+ # @private
42
43
  def ==(other)
43
44
  other == @id
44
45
  end
@@ -53,41 +54,61 @@ module Cassandra
53
54
 
54
55
  # @private
55
56
  def initialize(id, client)
56
- @id = id
57
- @client = client
57
+ @id = id
58
+ @client = client
59
+ @coordinator = nil
60
+ @duration = nil
61
+ @parameters = nil
62
+ @request = nil
63
+ @started_at = nil
64
+ @events = nil
65
+ @client_ip = nil
66
+ @loaded = false
67
+ @loaded_events = false
58
68
 
59
69
  mon_initialize
60
70
  end
61
71
 
62
- # Returns the ip of coordinator node. Typically the same as {Cassandra::Execution::Info#hosts}`.last`
72
+ # Returns the ip of coordinator node. Typically the same as
73
+ # {Cassandra::Execution::Info#hosts}`.last`
63
74
  #
64
75
  # @return [IPAddr] ip of the coordinator node
65
76
  def coordinator
66
- load unless @coordinator
77
+ load unless @loaded
67
78
 
68
79
  @coordinator
69
80
  end
70
81
 
82
+ # Returns the ip of the client node, the node that ran the driver
83
+ # instance that started tracing.
84
+ #
85
+ # @return [IPAddr, nil] ip of the client node running the driver
86
+ def client
87
+ load unless @loaded
88
+
89
+ @client_ip
90
+ end
91
+
71
92
  def duration
72
- load unless @duration
93
+ load unless @loaded
73
94
 
74
95
  @duration
75
96
  end
76
97
 
77
98
  def parameters
78
- load unless @parameters
99
+ load unless @loaded
79
100
 
80
101
  @parameters
81
102
  end
82
103
 
83
104
  def request
84
- load unless @request
105
+ load unless @loaded
85
106
 
86
107
  @request
87
108
  end
88
109
 
89
110
  def started_at
90
- load unless @started_at
111
+ load unless @loaded
91
112
 
92
113
  @started_at
93
114
  end
@@ -96,11 +117,12 @@ module Cassandra
96
117
  #
97
118
  # @return [Array<Cassandra::Execution::Trace::Event>] events
98
119
  def events
99
- load_events unless @events
120
+ load_events unless @loaded_events
100
121
 
101
122
  @events
102
123
  end
103
124
 
125
+ # @private
104
126
  def inspect
105
127
  "#<#{self.class.name}:0x#{self.object_id.to_s(16)} @id=#{@id.inspect}>"
106
128
  end
@@ -117,7 +139,16 @@ module Cassandra
117
139
  synchronize do
118
140
  return if @loaded
119
141
 
120
- data = @client.query(Statements::Simple.new(SELECT_SESSION % @id), VOID_OPTIONS).get.first
142
+ attempt = 1
143
+ data = @client.query(Statements::Simple.new(SELECT_SESSION % @id), VOID_OPTIONS).get.first
144
+
145
+ while data.nil? && attempt <= 5
146
+ sleep(attempt * 0.4)
147
+ data = @client.query(Statements::Simple.new(SELECT_SESSION % @id), VOID_OPTIONS).get.first
148
+ break if data
149
+ attempt += 1
150
+ end
151
+
121
152
  raise ::RuntimeError, "unable to load trace #{@id}" if data.nil?
122
153
 
123
154
  @coordinator = data['coordinator']
@@ -125,6 +156,7 @@ module Cassandra
125
156
  @parameters = data['parameters']
126
157
  @request = data['request']
127
158
  @started_at = data['started_at']
159
+ @client_ip = data['client']
128
160
  @loaded = true
129
161
  end
130
162
 
@@ -0,0 +1,150 @@
1
+ # encoding: utf-8
2
+
3
+ #--
4
+ # Copyright 2013-2015 DataStax, Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #++
18
+
19
+ module Cassandra
20
+ # Represents a cassandra user defined function
21
+ # @see Cassandra::Keyspace#each_function
22
+ # @see Cassandra::Keyspace#function
23
+ # @see Cassandra::Keyspace#has_function?
24
+ class Function
25
+ # @private
26
+ attr_reader :keyspace
27
+ # @return [String] function name
28
+ attr_reader :name
29
+ # @return [String] function language
30
+ attr_reader :language
31
+ # @return [Cassandra::Type] function return type
32
+ attr_reader :type
33
+ # @return [String] function body
34
+ attr_reader :body
35
+
36
+ # @private
37
+ def initialize(keyspace, name, language, type, arguments, body, called_on_null)
38
+ @keyspace = keyspace
39
+ @name = name
40
+ @language = language
41
+ @type = type
42
+ @arguments = arguments
43
+ @body = body
44
+ @called_on_null = called_on_null
45
+
46
+ # Build up an arguments hash keyed on arg-name.
47
+ @arguments_hash = @arguments.each_with_object({}) do |arg, h|
48
+ h[arg.name] = arg
49
+ end
50
+ end
51
+
52
+ # @return [Boolean] whether this function will be called on null input
53
+ def called_on_null?
54
+ @called_on_null
55
+ end
56
+
57
+ # @param name [String] argument name
58
+ # @return [Boolean] whether this function has a given argument
59
+ def has_argument?(name)
60
+ @arguments_hash.has_key?(name)
61
+ end
62
+
63
+ # @param name [String] argument name
64
+ # @return [Cassandra::Argument, nil] an argument or nil
65
+ def argument(name)
66
+ @arguments_hash[name]
67
+ end
68
+
69
+ # Yield or enumerate each argument defined in this function
70
+ # @overload each_argument
71
+ # @yieldparam argument [Cassandra::Argument] current argument
72
+ # @return [Cassandra::Table] self
73
+ # @overload each_argument
74
+ # @return [Array<Cassandra::Argument>] a list of arguments
75
+ def each_argument(&block)
76
+ if block_given?
77
+ @arguments.each(&block)
78
+ self
79
+ else
80
+ # We return a dup of the arguments so that the caller can manipulate
81
+ # the array however they want without affecting the source.
82
+ @arguments.dup
83
+ end
84
+ end
85
+ alias :arguments :each_argument
86
+
87
+ # Get the list of argument types for this function.
88
+ # @return [Array<Cassandra::Type>] a list of argument types.
89
+ def argument_types
90
+ @arguments.map { |argument| argument.type }
91
+ end
92
+
93
+ # @private
94
+ def eql?(other)
95
+ other.is_a?(Function) && \
96
+ @keyspace == other.keyspace && \
97
+ @name == other.name && \
98
+ @language == other.language && \
99
+ @type == other.type && \
100
+ @arguments == other.arguments && \
101
+ @body == other.body && \
102
+ @called_on_null == other.called_on_null?
103
+ end
104
+ alias :== :eql?
105
+
106
+ # @private
107
+ def hash
108
+ @hash ||= begin
109
+ h = 17
110
+ h = 31 * h + @keyspace.hash
111
+ h = 31 * h + @name.hash
112
+ h = 31 * h + @language.hash
113
+ h = 31 * h + @type.hash
114
+ h = 31 * h + @arguments.hash
115
+ h = 31 * h + @body.hash
116
+ h = 31 * h + @called_on_null.hash
117
+ h
118
+ end
119
+ end
120
+
121
+ # @private
122
+ def inspect
123
+ "#<Cassandra::Function:0x#{self.object_id.to_s(16)} @keyspace=#{@keyspace.inspect}, @name=#{@name.inspect}, @language=#{@language.inspect}, @type=#{@type.inspect}, @arguments=#{@arguments.inspect} @body=#{@body.inspect}>"
124
+ end
125
+
126
+ # @return [String] a cql representation of this function
127
+ def to_cql
128
+ cql = "CREATE FUNCTION #{Util.escape_name(@keyspace)}.#{Util.escape_name(@name)}("
129
+ first = true
130
+ @arguments.each do |argument|
131
+ if first
132
+ first = false
133
+ else
134
+ cql << ', '
135
+ end
136
+ cql << "#{argument.name} #{argument.type}"
137
+ end
138
+ cql << ")"
139
+ if @called_on_null
140
+ cql << "\n CALLED ON NULL INPUT"
141
+ else
142
+ cql << "\n RETURNS NULL ON NULL INPUT"
143
+ end
144
+ cql << "\n RETURNS #{@type}"
145
+ cql << "\n LANGUAGE #{@language}"
146
+ cql << "\n AS $$#{@body}$$"
147
+ cql << ";"
148
+ end
149
+ end
150
+ end
@@ -51,10 +51,12 @@ module Cassandra
51
51
  @error = error
52
52
  end
53
53
 
54
- def get
54
+ def get(timeout = nil)
55
55
  raise(@error, @error.message, @error.backtrace)
56
56
  end
57
57
 
58
+ alias :join :get
59
+
58
60
  def on_success
59
61
  raise ::ArgumentError, "no block given" unless block_given?
60
62
  self
@@ -81,10 +83,6 @@ module Cassandra
81
83
  self
82
84
  end
83
85
 
84
- def join
85
- self
86
- end
87
-
88
86
  def then
89
87
  raise ::ArgumentError, "no block given" unless block_given?
90
88
  self
@@ -109,10 +107,12 @@ module Cassandra
109
107
  @value = value
110
108
  end
111
109
 
112
- def get
110
+ def get(timeout = nil)
113
111
  @value
114
112
  end
115
113
 
114
+ alias :join :get
115
+
116
116
  def on_success
117
117
  raise ::ArgumentError, "no block given" unless block_given?
118
118
  yield(@value) rescue nil
@@ -356,23 +356,23 @@ module Cassandra
356
356
  end
357
357
 
358
358
  # Returns future value or raises future error
359
- # @note This method blocks until a future is resolved
360
- # @raise [Exception] error used to resolve this future if any
361
- # @return [Object] value used to resolve this future if any
362
- def get
363
- @signal.get
364
-
359
+ #
360
+ # @note This method blocks until a future is resolved or a times out
361
+ #
362
+ # @param timeout [nil, Numeric] a maximum number of seconds to block
363
+ # current thread for while waiting for this future to resolve. Will
364
+ # wait indefinitely if passed `nil`.
365
+ #
366
+ # @raise [Errors::TimeoutError] raised when wait time exceeds the timeout
367
+ # @raise [Exception] raises when the future has been resolved with an
368
+ # error. The original exception will be raised.
369
+ #
370
+ # @return [Object] the value that the future has been resolved with
371
+ def get(timeout = nil)
372
+ @signal.get(timeout)
365
373
  end
366
374
 
367
- # Block until the future has been resolved
368
- # @note This method blocks until a future is resolved
369
- # @note This method won't raise any errors or return anything but the
370
- # future itself
371
- # @return [self]
372
- def join
373
- @signal.join
374
- self
375
- end
375
+ alias :join :get
376
376
  end
377
377
 
378
378
  # @private
@@ -550,28 +550,59 @@ module Cassandra
550
550
  self
551
551
  end
552
552
 
553
- def join
554
- return unless @state == :pending
555
-
556
- synchronize do
557
- return unless @state == :pending
558
-
559
- @waiting += 1
560
- @cond.wait while @state == :pending
561
- @waiting -= 1
553
+ # @param timeout [nil, Numeric] a maximum number of seconds to block
554
+ # current thread for while waiting for this future to resolve. Will
555
+ # wait indefinitely if passed `nil`.
556
+ #
557
+ # @raise [ArgumentError] raised when a negative timeout is given
558
+ # @raise [Errors::TimeoutError] raised when wait time exceeds the timeout
559
+ # @raise [Exception] raises when the future has been resolved with an
560
+ # error. The original exception will be raised.
561
+ #
562
+ # @return [Object] the value that the future has been resolved with
563
+ def get(timeout = nil)
564
+ timeout = timeout && Float(timeout)
565
+
566
+ if timeout
567
+ raise ::ArgumentError, "timeout cannot be negative, #{timeout.inspect} given" if timeout < 0
568
+
569
+ start = ::Time.now
570
+ now = start
571
+ deadline = start + timeout
562
572
  end
563
573
 
564
- nil
565
- end
574
+ if @state == :pending
575
+ synchronize do
576
+ if @state == :pending
577
+ @waiting += 1
578
+ while @state == :pending
579
+ if deadline
580
+ @cond.wait(deadline - now)
581
+ now = ::Time.now
582
+ break if now >= deadline
583
+ else
584
+ @cond.wait
585
+ end
586
+ end
587
+ @waiting -= 1
588
+ end
589
+ end
566
590
 
567
- def get
568
- join if @state == :pending
591
+ if @state == :pending
592
+ total_wait = deadline - start
593
+ raise Errors::TimeoutError, "Future did not complete within #{timeout.inspect} seconds. Wait time: #{total_wait.inspect}"
594
+ end
595
+ end
569
596
 
570
- raise(@error, @error.message, @error.backtrace) if @state == :broken
597
+ if @state == :broken
598
+ raise(@error, @error.message, @error.backtrace)
599
+ end
571
600
 
572
601
  @value
573
602
  end
574
603
 
604
+ alias :join :get
605
+
575
606
  def add_listener(listener)
576
607
  if @state == :pending
577
608
  synchronize do