cassandra-driver 3.0.3 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +8 -8
  2. data/README.md +46 -31
  3. data/lib/cassandra.rb +35 -44
  4. data/lib/cassandra/cluster.rb +40 -11
  5. data/lib/cassandra/cluster/client.rb +193 -159
  6. data/lib/cassandra/cluster/connector.rb +12 -10
  7. data/lib/cassandra/cluster/control_connection.rb +38 -10
  8. data/lib/cassandra/cluster/options.rb +8 -4
  9. data/lib/cassandra/cluster/registry.rb +1 -2
  10. data/lib/cassandra/cluster/schema/fetchers.rb +122 -26
  11. data/lib/cassandra/column_container.rb +9 -4
  12. data/lib/cassandra/custom_data.rb +24 -22
  13. data/lib/cassandra/driver.rb +30 -13
  14. data/lib/cassandra/errors.rb +12 -2
  15. data/lib/cassandra/execution/options.rb +52 -16
  16. data/lib/cassandra/execution/profile.rb +150 -0
  17. data/lib/cassandra/execution/profile_manager.rb +71 -0
  18. data/lib/cassandra/execution/trace.rb +5 -4
  19. data/lib/cassandra/executors.rb +1 -1
  20. data/lib/cassandra/index.rb +1 -1
  21. data/lib/cassandra/keyspace.rb +36 -1
  22. data/lib/cassandra/protocol.rb +5 -0
  23. data/lib/cassandra/protocol/coder.rb +2 -1
  24. data/lib/cassandra/protocol/cql_byte_buffer.rb +21 -0
  25. data/lib/cassandra/protocol/responses/read_failure_error_response.rb +10 -4
  26. data/lib/cassandra/protocol/responses/write_failure_error_response.rb +14 -8
  27. data/lib/cassandra/protocol/v3.rb +2 -1
  28. data/lib/cassandra/protocol/v4.rb +58 -20
  29. data/lib/cassandra/result.rb +1 -1
  30. data/lib/cassandra/session.rb +43 -16
  31. data/lib/cassandra/statements/bound.rb +5 -1
  32. data/lib/cassandra/statements/prepared.rb +8 -3
  33. data/lib/cassandra/table.rb +72 -0
  34. data/lib/cassandra/trigger.rb +67 -0
  35. data/lib/cassandra/types.rb +12 -24
  36. data/lib/cassandra/udt.rb +3 -6
  37. data/lib/cassandra/uuid/generator.rb +6 -3
  38. data/lib/cassandra/version.rb +1 -1
  39. metadata +5 -2
@@ -32,7 +32,7 @@ module Cassandra
32
32
  @consistency,
33
33
  @retries,
34
34
  @trace_id ?
35
- Execution::Trace.new(@trace_id, @client) :
35
+ Execution::Trace.new(@trace_id, @client, @options.load_balancing_policy) :
36
36
  nil)
37
37
  end
38
38
 
@@ -29,10 +29,11 @@ module Cassandra
29
29
  def_delegators :@client, :keyspace
30
30
 
31
31
  # @private
32
- def initialize(client, default_options, futures_factory)
32
+ def initialize(client, default_options, futures_factory, profile_manager)
33
33
  @client = client
34
34
  @options = default_options
35
35
  @futures = futures_factory
36
+ @profile_manager = profile_manager
36
37
  end
37
38
 
38
39
  # Executes a given statement and returns a future result
@@ -66,6 +67,8 @@ module Cassandra
66
67
  # statement can be retried safely on timeout.
67
68
  # @option options [Hash<[String, Symbol], String>] :payload (nil) custom
68
69
  # outgoing payload to be sent with the request.
70
+ # @option options [String, Symbol] :execution_profile (nil) name of {Cassandra::Execution::Profile}
71
+ # from which to obtain certain query options. Defaults to the cluster's default execution profile.
69
72
  #
70
73
  # @see Cassandra.cluster Options that can be specified on the cluster-level
71
74
  # and their default values.
@@ -78,14 +81,9 @@ module Cassandra
78
81
  #
79
82
  # @return [Cassandra::Future<Cassandra::Result>]
80
83
  #
81
- # @see Cassandra::Session#execute A list of errors this future can be
82
- # resolved with
84
+ # @see Cassandra::Session#execute A list of errors this future can be resolved with
83
85
  def execute_async(statement, options = nil)
84
- options = if options
85
- @options.override(options)
86
- else
87
- @options
88
- end
86
+ options = merge_execution_options(options)
89
87
 
90
88
  case statement
91
89
  when ::String
@@ -110,7 +108,7 @@ module Cassandra
110
108
  # Cassandra::Statements::Bound, Cassandra::Statements::Prepared]
111
109
  # statement to execute
112
110
  #
113
- # @param options [Hash] (nil) a customizable set of options
111
+ # @param options [Hash] (nil) a customizable set of options. See {#execute_async} for details.
114
112
  #
115
113
  # @see Cassandra::Session#execute_async
116
114
  # @see Cassandra::Future#get
@@ -138,15 +136,13 @@ module Cassandra
138
136
  # @option options [Boolean] :idempotent (false) specify whether the
139
137
  # statement being prepared can be retried safely on timeout during
140
138
  # execution.
139
+ # @option options [String, Symbol] :execution_profile (nil) name of {Cassandra::Execution::Profile}
140
+ # from which to obtain certain query options. Defaults to the cluster's default execution profile.
141
141
  #
142
142
  # @return [Cassandra::Future<Cassandra::Statements::Prepared>] future
143
143
  # prepared statement
144
144
  def prepare_async(statement, options = nil)
145
- options = if options.is_a?(::Hash)
146
- @options.override(options)
147
- else
148
- @options
149
- end
145
+ options = merge_execution_options(options)
150
146
 
151
147
  case statement
152
148
  when ::String
@@ -161,14 +157,20 @@ module Cassandra
161
157
  end
162
158
 
163
159
  # A blocking wrapper around {Cassandra::Session#prepare_async}
160
+ #
161
+ # @param statement [String, Cassandra::Statements::Simple] a statement to
162
+ # prepare
163
+ #
164
+ # @param options [Hash] (nil) a customizable set of options. See {#prepare_async} for details.
165
+ #
164
166
  # @see Cassandra::Session#prepare_async
165
167
  # @see Cassandra::Future#get
166
168
  #
167
169
  # @return [Cassandra::Statements::Prepared] prepared statement
168
170
  # @raise [Cassandra::Errors::NoHostsAvailable] if none of the hosts can be reached
169
171
  # @raise [Cassandra::Errors::ExecutionError] if Cassandra returns an error response
170
- def prepare(*args)
171
- prepare_async(*args).get
172
+ def prepare(statement, options = nil)
173
+ prepare_async(statement, options).get
172
174
  end
173
175
 
174
176
  # Returns a logged {Statements::Batch} instance and optionally yields it to
@@ -235,5 +237,30 @@ module Cassandra
235
237
  "@keyspace=#{keyspace.inspect}, " \
236
238
  "@options=#{@options.inspect}>"
237
239
  end
240
+
241
+ private
242
+
243
+ # @private
244
+ def merge_execution_options(options)
245
+ if options
246
+ Util.assert_instance_of(::Hash, options, "options must be a Hash, #{options.inspect} given")
247
+ # Yell if the caller gave us a bad profile name.
248
+ execution_profile = nil
249
+ if options.key?(:execution_profile)
250
+ execution_profile = @profile_manager.profiles[options[:execution_profile]]
251
+ raise ::ArgumentError.new("Unknown execution profile #{options[:execution_profile]}") unless execution_profile
252
+ end
253
+
254
+ # This looks a little hokey, so let's explain: Execution::Options.override takes a
255
+ # varargs-style array of things to merge into the base options object (to produce
256
+ # a new Options object, not mutate the base). If an execution profile was specified,
257
+ # we want its attributes to override the base options. In addition, if individual options
258
+ # were specified, we want *those* to take precedence over the execution profile attributes.
259
+ # So we override in this order.
260
+ @options.override(execution_profile, options)
261
+ else
262
+ @options
263
+ end
264
+ end
238
265
  end
239
266
  end
@@ -28,15 +28,19 @@ module Cassandra
28
28
  attr_reader :params
29
29
  # @private
30
30
  attr_reader :params_types, :result_metadata, :keyspace, :partition_key
31
+ # @private prepared-statement id
32
+ attr_reader :id
31
33
 
32
34
  # @private
33
- def initialize(cql,
35
+ def initialize(id,
36
+ cql,
34
37
  params_types,
35
38
  result_metadata,
36
39
  params,
37
40
  keyspace = nil,
38
41
  partition_key = nil,
39
42
  idempotent = false)
43
+ @id = id
40
44
  @cql = cql
41
45
  @params_types = params_types
42
46
  @result_metadata = result_metadata
@@ -27,9 +27,12 @@ module Cassandra
27
27
  attr_reader :cql
28
28
  # @private
29
29
  attr_reader :result_metadata
30
+ # @private prepared-statement id
31
+ attr_reader :id
30
32
 
31
33
  # @private
32
- def initialize(payload,
34
+ def initialize(id,
35
+ payload,
33
36
  warnings,
34
37
  cql,
35
38
  params_metadata,
@@ -44,6 +47,7 @@ module Cassandra
44
47
  retries,
45
48
  client,
46
49
  connection_options)
50
+ @id = id
47
51
  @payload = payload
48
52
  @warnings = warnings
49
53
  @cql = cql
@@ -131,7 +135,8 @@ module Cassandra
131
135
 
132
136
  partition_key = create_partition_key(params)
133
137
 
134
- Bound.new(@cql,
138
+ Bound.new(@id,
139
+ @cql,
135
140
  param_types,
136
141
  @result_metadata,
137
142
  params,
@@ -151,7 +156,7 @@ module Cassandra
151
156
  @consistency,
152
157
  @retries,
153
158
  @trace_id ?
154
- Execution::Trace.new(@trace_id, @client) :
159
+ Execution::Trace.new(@trace_id, @client, @options.load_balancing_policy) :
155
160
  nil)
156
161
  end
157
162
 
@@ -38,6 +38,10 @@ module Cassandra
38
38
  @clustering_order = clustering_order.freeze
39
39
  @indexes = []
40
40
  @indexes_hash = {}
41
+ @materialized_views = []
42
+ @materialized_views_hash = {}
43
+ @triggers = []
44
+ @triggers_hash = {}
41
45
  end
42
46
 
43
47
  # @param name [String] index name
@@ -68,6 +72,62 @@ module Cassandra
68
72
  end
69
73
  alias indexes each_index
70
74
 
75
+ # @param name [String] trigger name
76
+ # @return [Boolean] whether this table has a given trigger
77
+ def has_trigger?(name)
78
+ @triggers_hash.key?(name)
79
+ end
80
+
81
+ # @param name [String] trigger name
82
+ # @return [Cassandra::Trigger, nil] a trigger or nil
83
+ def trigger(name)
84
+ @triggers_hash[name]
85
+ end
86
+
87
+ # Yield or enumerate each trigger bound to this table
88
+ # @overload each_trigger
89
+ # @yieldparam trigger [Cassandra::Index] current trigger
90
+ # @return [Cassandra::Table] self
91
+ # @overload each_trigger
92
+ # @return [Array<Cassandra::Trigger>] a list of triggers
93
+ def each_trigger(&block)
94
+ if block_given?
95
+ @triggers.each(&block)
96
+ self
97
+ else
98
+ @triggers.freeze
99
+ end
100
+ end
101
+ alias triggers each_trigger
102
+
103
+ # @param name [String] materialized view name
104
+ # @return [Boolean] whether this table has a given materialized view
105
+ def has_materialized_view?(name)
106
+ @materialized_views_hash.key?(name)
107
+ end
108
+
109
+ # @param name [String] materialized view name
110
+ # @return [Cassandra::MaterializedView, nil] a materialized view or nil
111
+ def materialized_view(name)
112
+ @materialized_views_hash[name]
113
+ end
114
+
115
+ # Yield or enumerate each materialized view bound to this table
116
+ # @overload each_materialized_view
117
+ # @yieldparam materialized_view [Cassandra::MaterializedView] current materialized view
118
+ # @return [Cassandra::Table] self
119
+ # @overload each_materialized_view
120
+ # @return [Array<Cassandra::MaterializedView>] a list of materialized views
121
+ def each_materialized_view(&block)
122
+ if block_given?
123
+ @materialized_views.each(&block)
124
+ self
125
+ else
126
+ @materialized_views.freeze
127
+ end
128
+ end
129
+ alias materialized_views each_materialized_view
130
+
71
131
  # @return [String] a cql representation of this table
72
132
  def to_cql
73
133
  cql = "CREATE TABLE #{Util.escape_name(@keyspace.name)}.#{Util.escape_name(@name)} (\n"
@@ -134,6 +194,18 @@ module Cassandra
134
194
  @indexes_hash[index.name] = index
135
195
  end
136
196
 
197
+ # @private
198
+ def add_view(view)
199
+ @materialized_views << view
200
+ @materialized_views_hash[view.name] = view
201
+ end
202
+
203
+ # @private
204
+ def add_trigger(trigger)
205
+ @triggers << trigger
206
+ @triggers_hash[trigger.name] = trigger
207
+ end
208
+
137
209
  # @private
138
210
  def eql?(other)
139
211
  other.is_a?(Table) &&
@@ -0,0 +1,67 @@
1
+ # encoding: utf-8
2
+
3
+ #--
4
+ # Copyright 2013-2016 DataStax, Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #++
18
+
19
+ module Cassandra
20
+ # Represents a trigger on a cassandra table
21
+ class Trigger
22
+ # @return [Cassandra::Table] table that the trigger applies to.
23
+ attr_reader :table
24
+ # @return [String] name of the trigger.
25
+ attr_reader :name
26
+ # @return [Hash] options of the trigger.
27
+ attr_reader :options
28
+
29
+ # @private
30
+ def initialize(table,
31
+ name,
32
+ options)
33
+ @table = table
34
+ @name = name.freeze
35
+ @options = options.freeze
36
+ end
37
+
38
+ # @return [String] name of the trigger class
39
+ def custom_class_name
40
+ @options['class']
41
+ end
42
+
43
+ # @return [String] a cql representation of this trigger
44
+ def to_cql
45
+ keyspace_name = Util.escape_name(@table.keyspace.name)
46
+ table_name = Util.escape_name(@table.name)
47
+ trigger_name = Util.escape_name(@name)
48
+
49
+ "CREATE TRIGGER #{trigger_name} ON #{keyspace_name}.#{table_name} USING '#{@options['class']}';"
50
+ end
51
+
52
+ # @private
53
+ def eql?(other)
54
+ other.is_a?(Trigger) &&
55
+ @table == other.table &&
56
+ @name == other.name &&
57
+ @options == other.options
58
+ end
59
+ alias == eql?
60
+
61
+ # @private
62
+ def inspect
63
+ "#<#{self.class.name}:0x#{object_id.to_s(16)} " \
64
+ "@name=#{@name.inspect} @table=#{@table.inspect} @options=#{@options.inspect}>"
65
+ end
66
+ end
67
+ end
@@ -1518,8 +1518,7 @@ module Cassandra
1518
1518
  # @return [Cassandra::Types::List] list type
1519
1519
  def list(value_type)
1520
1520
  Util.assert_instance_of(Cassandra::Type, value_type,
1521
- "list type must be a Cassandra::Type, #{value_type.inspect} given"
1522
- )
1521
+ "list type must be a Cassandra::Type, #{value_type.inspect} given")
1523
1522
 
1524
1523
  List.new(value_type)
1525
1524
  end
@@ -1529,11 +1528,9 @@ module Cassandra
1529
1528
  # @return [Cassandra::Types::Map] map type
1530
1529
  def map(key_type, value_type)
1531
1530
  Util.assert_instance_of(Cassandra::Type, key_type,
1532
- "map key type must be a Cassandra::Type, #{key_type.inspect} given"
1533
- )
1531
+ "map key type must be a Cassandra::Type, #{key_type.inspect} given")
1534
1532
  Util.assert_instance_of(Cassandra::Type, value_type,
1535
- "map value type must be a Cassandra::Type, #{value_type.inspect} given"
1536
- )
1533
+ "map value type must be a Cassandra::Type, #{value_type.inspect} given")
1537
1534
 
1538
1535
  Map.new(key_type, value_type)
1539
1536
  end
@@ -1542,8 +1539,7 @@ module Cassandra
1542
1539
  # @return [Cassandra::Types::Set] set type
1543
1540
  def set(value_type)
1544
1541
  Util.assert_instance_of(Cassandra::Type, value_type,
1545
- "set type must be a Cassandra::Type, #{value_type.inspect} given"
1546
- )
1542
+ "set type must be a Cassandra::Type, #{value_type.inspect} given")
1547
1543
 
1548
1544
  Set.new(value_type)
1549
1545
  end
@@ -1555,8 +1551,7 @@ module Cassandra
1555
1551
  members.each do |member|
1556
1552
  Util.assert_instance_of(Cassandra::Type, member,
1557
1553
  'each tuple member must be a Cassandra::Type, ' \
1558
- "#{member.inspect} given"
1559
- )
1554
+ "#{member.inspect} given")
1560
1555
  end
1561
1556
 
1562
1557
  Tuple.new(*members)
@@ -1598,40 +1593,33 @@ module Cassandra
1598
1593
  fields = Array(fields.first) if fields.one?
1599
1594
 
1600
1595
  Util.assert_not_empty(fields,
1601
- 'user-defined type must contain at least one field'
1602
- )
1596
+ 'user-defined type must contain at least one field')
1603
1597
 
1604
1598
  if fields.first.is_a?(::Array)
1605
1599
  fields = fields.map do |pair|
1606
1600
  Util.assert(pair.size == 2,
1607
1601
  'fields of a user-defined type must be an Array of name and ' \
1608
- "value pairs, #{pair.inspect} given"
1609
- )
1602
+ "value pairs, #{pair.inspect} given")
1610
1603
  Util.assert_instance_of(::String, pair[0],
1611
1604
  'each field name for a user-defined type must be a String, ' \
1612
- "#{pair[0].inspect} given"
1613
- )
1605
+ "#{pair[0].inspect} given")
1614
1606
  Util.assert_instance_of(Cassandra::Type, pair[1],
1615
1607
  'each field type for a user-defined type must be a ' \
1616
- "Cassandra::Type, #{pair[1].inspect} given"
1617
- )
1608
+ "Cassandra::Type, #{pair[1].inspect} given")
1618
1609
 
1619
1610
  UserDefined::Field.new(*pair)
1620
1611
  end
1621
1612
  else
1622
1613
  Util.assert(fields.size.even?,
1623
1614
  'fields of a user-defined type must be an Array of alternating ' \
1624
- "names and values pairs, #{fields.inspect} given"
1625
- )
1615
+ "names and values pairs, #{fields.inspect} given")
1626
1616
  fields = fields.each_slice(2).map do |field_name, field_type|
1627
1617
  Util.assert_instance_of(::String, field_name,
1628
1618
  'each field name for a user-defined type must be a String, ' \
1629
- "#{field_name.inspect} given"
1630
- )
1619
+ "#{field_name.inspect} given")
1631
1620
  Util.assert_instance_of(Cassandra::Type, field_type,
1632
1621
  'each field type for a user-defined type must be a ' \
1633
- "Cassandra::Type, #{field_type.inspect} given"
1634
- )
1622
+ "Cassandra::Type, #{field_type.inspect} given")
1635
1623
 
1636
1624
  UserDefined::Field.new(field_name, field_type)
1637
1625
  end
@@ -224,15 +224,13 @@ module Cassandra
224
224
  values = Array(values.first) if values.one?
225
225
 
226
226
  Util.assert_not_empty(values,
227
- 'user-defined type must contain at least one value'
228
- )
227
+ 'user-defined type must contain at least one value')
229
228
 
230
229
  if values.first.is_a?(::Array)
231
230
  @values = values.map do |pair|
232
231
  Util.assert(pair.size == 2,
233
232
  'values of a user-defined type must be an Array of name and ' \
234
- "value pairs, #{pair.inspect} given"
235
- )
233
+ "value pairs, #{pair.inspect} given")
236
234
  name, value = pair
237
235
 
238
236
  [String(name), value]
@@ -240,8 +238,7 @@ module Cassandra
240
238
  else
241
239
  Util.assert(values.size.even?,
242
240
  'values of a user-defined type must be an Array of alternating ' \
243
- "names and values pairs, #{values.inspect} given"
244
- )
241
+ "names and values pairs, #{values.inspect} given")
245
242
  @values = values.each_slice(2).map do |(name, value)|
246
243
  [String(name), value]
247
244
  end