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.
- checksums.yaml +8 -8
- data/README.md +46 -31
- data/lib/cassandra.rb +35 -44
- data/lib/cassandra/cluster.rb +40 -11
- data/lib/cassandra/cluster/client.rb +193 -159
- data/lib/cassandra/cluster/connector.rb +12 -10
- data/lib/cassandra/cluster/control_connection.rb +38 -10
- data/lib/cassandra/cluster/options.rb +8 -4
- data/lib/cassandra/cluster/registry.rb +1 -2
- data/lib/cassandra/cluster/schema/fetchers.rb +122 -26
- data/lib/cassandra/column_container.rb +9 -4
- data/lib/cassandra/custom_data.rb +24 -22
- data/lib/cassandra/driver.rb +30 -13
- data/lib/cassandra/errors.rb +12 -2
- data/lib/cassandra/execution/options.rb +52 -16
- data/lib/cassandra/execution/profile.rb +150 -0
- data/lib/cassandra/execution/profile_manager.rb +71 -0
- data/lib/cassandra/execution/trace.rb +5 -4
- data/lib/cassandra/executors.rb +1 -1
- data/lib/cassandra/index.rb +1 -1
- data/lib/cassandra/keyspace.rb +36 -1
- data/lib/cassandra/protocol.rb +5 -0
- data/lib/cassandra/protocol/coder.rb +2 -1
- data/lib/cassandra/protocol/cql_byte_buffer.rb +21 -0
- data/lib/cassandra/protocol/responses/read_failure_error_response.rb +10 -4
- data/lib/cassandra/protocol/responses/write_failure_error_response.rb +14 -8
- data/lib/cassandra/protocol/v3.rb +2 -1
- data/lib/cassandra/protocol/v4.rb +58 -20
- data/lib/cassandra/result.rb +1 -1
- data/lib/cassandra/session.rb +43 -16
- data/lib/cassandra/statements/bound.rb +5 -1
- data/lib/cassandra/statements/prepared.rb +8 -3
- data/lib/cassandra/table.rb +72 -0
- data/lib/cassandra/trigger.rb +67 -0
- data/lib/cassandra/types.rb +12 -24
- data/lib/cassandra/udt.rb +3 -6
- data/lib/cassandra/uuid/generator.rb +6 -3
- data/lib/cassandra/version.rb +1 -1
- metadata +5 -2
data/lib/cassandra/result.rb
CHANGED
data/lib/cassandra/session.rb
CHANGED
@@ -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 =
|
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 =
|
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(
|
171
|
-
prepare_async(
|
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(
|
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(
|
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(@
|
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
|
|
data/lib/cassandra/table.rb
CHANGED
@@ -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
|
data/lib/cassandra/types.rb
CHANGED
@@ -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
|
data/lib/cassandra/udt.rb
CHANGED
@@ -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
|